Пример или объяснение Core Data Migration с несколькими проходами? - PullRequest
84 голосов
/ 13 мая 2011

Моему приложению для iPhone необходимо перенести основное хранилище данных, а некоторые базы данных достаточно велики. В документации Apple предлагается использовать "несколько проходов" для переноса данных с целью сокращения использования памяти. Однако документация очень ограничена и не очень хорошо объясняет, как на самом деле это сделать.Может ли кто-нибудь указать мне хороший пример или подробно объяснить, как на самом деле это осуществить?

Ответы [ 3 ]

171 голосов
/ 24 мая 2011

Я понял, что Apple намекает в их документации . Это на самом деле очень легко, но долгий путь, прежде чем он станет очевидным. Я проиллюстрирую объяснение на примере. Исходная ситуация такова:

Модель данных версии 1

enter image description here enter image description here

Это модель, которую вы получаете, когда создаете проект с помощью шаблона «приложение на основе навигации с основным хранилищем данных». Я скомпилировал его и сделал несколько сложных ударов с помощью цикла for, чтобы создать около 2 тыс. Записей с разными значениями. Там мы идем 2000 событий со значением NSDate.

Теперь мы добавим вторую версию модели данных, которая выглядит следующим образом:

enter image description here

Модель данных версии 2

Разница в том, что сущность Event исчезла, и у нас есть две новые. Тот, который хранит метку времени как double, а второй, который должен хранить дату как NSString.

Цель состоит в том, чтобы передать все Версия 1 События двум новым объектам и преобразовать значения в процессе миграции. Это приводит к удвоению значений каждого из них как отдельного типа в отдельном объекте.

Для миграции мы выбираем миграцию вручную, и это мы делаем с моделями сопоставления. Это также первая часть ответа на ваш вопрос. Мы сделаем миграцию в два этапа, потому что миграция записей 2k занимает много времени, и мы хотим, чтобы объем памяти оставался низким.

Вы могли бы даже пойти дальше и разделить эти модели отображения дальше, чтобы перенести только диапазоны сущностей. Скажем, у нас есть миллион записей, это может привести к краху всего процесса. Можно сузить выбранные объекты с помощью предиката Filter .

Вернуться к нашим двум картографическим моделям.

Мы создаем первую модель отображения следующим образом:

1. Новый файл -> Ресурс -> Модель отображения enter image description here

2. Выберите имя, я выбрал StepOne

3. Установить исходную и целевую модель данных

enter image description here

Модель отображения Шаг первый

enter image description here

enter image description here

enter image description here

Многопроходная миграция не требует настраиваемых политик миграции сущностей, однако мы сделаем это, чтобы получить немного больше подробностей для этого примера. Таким образом, мы добавляем пользовательскую политику к объекту. Это всегда подкласс NSEntityMigrationPolicy.

enter image description here

Этот класс политики реализует несколько методов, позволяющих осуществить нашу миграцию. Однако в этом случае все просто, поэтому нам придется реализовать только один метод: createDestinationInstancesForSourceInstance:entityMapping:manager:error:.

Реализация будет выглядеть так:

StepOneEntityMigrationPolicy.m

#import "StepOneEntityMigrationPolicy.h"


@implementation StepOneEntityMigrationPolicy

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance 
                                      entityMapping:(NSEntityMapping *)mapping 
                                            manager:(NSMigrationManager *)manager 
                                              error:(NSError **)error
{
    // Create a new object for the model context
    NSManagedObject *newObject = 
        [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] 
                                      inManagedObjectContext:[manager destinationContext]];

    // do our transfer of nsdate to nsstring
    NSDate *date = [sInstance valueForKey:@"timeStamp"];
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
    [dateFormatter setDateStyle:NSDateFormatterMediumStyle];    

    // set the value for our new object
    [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
    [dateFormatter release];

    // do the coupling of old and new
    [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];

    return YES;
}

Последний шаг: сама миграция

Я пропущу часть для настройки второй модели сопоставления, которая почти идентична, просто timeIntervalSince1970, используемый для преобразования NSDate в double.

Наконец, нам нужно запустить миграцию. Я пока пропущу шаблонный код. Если вам это нужно, я выложу здесь. Его можно найти по адресу Настройка процесса миграции Это всего лишь слияние первых двух примеров кода. Третья и последняя часть будут изменены следующим образом: Вместо использования метода класса NSMappingModel class mappingModelFromBundles:forSourceModel:destinationModel: мы будем использовать initWithContentsOfURL: потому что метод класса вернет только одну, может быть, первую найденную модель отображения в комплекте.

Теперь у нас есть две модели отображения, которые можно использовать на каждом проходе цикла, и отправить метод migrate в менеджер миграции. Вот и все.

NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil];
NSDictionary *sourceStoreOptions = nil;

NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"];

NSString *destinationStoreType = NSSQLiteStoreType;

NSDictionary *destinationStoreOptions = nil;

for (NSString *mappingModelName in mappingModelNames) {
    NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"];

    NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL];

    BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                                               type:sourceStoreType
                                            options:sourceStoreOptions
                                   withMappingModel:mappingModel
                                   toDestinationURL:destinationStoreURL
                                    destinationType:destinationStoreType
                                 destinationOptions:destinationStoreOptions
                                              error:&error2];
    [mappingModel release];
} 

Примечания

  • Модель отображения заканчивается на cdm в связке.

  • Должно быть указано хранилище назначения, и оно не должно быть хранилищем источника. После успешной миграции вы можете удалить старую и переименовать новую.

  • Я сделал некоторые изменения в модели данных после создания моделей отображения, это привело к некоторым ошибкам совместимости, которые я мог исправить только путем воссоздания моделей отображения.

3 голосов
/ 13 мая 2011

Эти вопросы связаны:

Проблемы с памятью при переносе больших хранилищ данных CoreData на iPhone

Многократная миграция данных ядра в блоках с iOS

Цитировать первую ссылку:

Это обсуждается в официальном документация в разделе «Несколько пропусков» раздел, однако это выглядит как их Предлагаемый подход состоит в том, чтобы разделить Ваша миграция по типу сущности, т.е. сделать несколько картографических моделей, каждая из которые переносят подмножество сущности типы из полной модели данных.

0 голосов
/ 21 мая 2011

Предположим, что в вашей схеме базы данных есть 5 сущностей, например, человек, студент, курс, класс и регистрация, чтобы использовать стандартный вид примера, где учащийся подкласса человек, класс реализует курс, а регистрация объединяет класс и студента.Если вы внесли изменения во все эти определения таблиц, вы должны начать с базовых классов и продолжить свой путь.Таким образом, вы не можете начать с преобразования регистраций, потому что каждая запись регистрации зависит от наличия класса и учеников там.Итак, вы бы начали с миграции только таблицы Person, копирования существующих строк в новую таблицу, заполнения всех новых полей (если это возможно) и удаления удаленных столбцов.Выполняйте каждую миграцию внутри пула автоматического выпуска, чтобы, как только это было сделано, ваша память снова начала работать.

После того, как таблица Person готова, вы можете преобразовать таблицу учеников.Затем прыгайте на курс, затем на класс и, наконец, на регистрационный стол.

Другим соображением является количество записей. Если, как Person имеет тысячу строк, вам придется каждые 100 или около того выполнять NSManagedObject, эквивалентный релизу, то есть сообщать контекст управляемого объекта [moc refreshObject: ob mergeChanges: NO];Также установите таймер устаревших данных на низкое значение, чтобы память часто очищалась.

...