Я разобрался, на что Apple намекает в своей документации . На самом деле это очень просто, но предстоит пройти долгий путь, прежде чем станет очевидным. Я проиллюстрирую объяснение на примере. Исходная ситуация такова:
Версия модели данных 1
Это модель, которую вы получаете, когда создаете проект с помощью шаблона «приложение на основе навигации с хранилищем основных данных». Я скомпилировал его и сильно ударил с помощью цикла for, чтобы создать около 2k записей с разными значениями. Теперь мы переходим к 2.000 событий со значением NSDate.
Теперь мы добавляем вторую версию модели данных, которая выглядит так:
Версия модели данных 2
Разница в том, что сущность Event исчезла, а у нас появилось две новых. Один хранит метку времени как a, double
а второй - дату как NSString
.
Цель состоит в том, чтобы перенести все события версии 1 в два новых объекта и преобразовать значения во время миграции. Это приводит к удвоению значений каждого отдельного типа в отдельной сущности.
Для миграции мы выбираем миграцию вручную, и это мы делаем с моделями сопоставления. Это тоже первая часть ответа на ваш вопрос. Мы выполним миграцию в два этапа, потому что перенос 2k записей занимает много времени, и нам нравится сохранять низкий объем памяти.
Вы даже можете пойти дальше и разделить эти модели отображения, чтобы перенести только диапазоны объектов. Допустим, у нас есть миллион записей, это может привести к сбою всего процесса. Можно сузить выборку сущностей с помощью предиката фильтра .
Вернемся к нашим двум моделям картографии.
Мы создаем первую модель отображения следующим образом:
1. Новый файл -> Ресурс -> Модель сопоставления
2. Выберите имя, я выбрал StepOne
3. Установите исходную и целевую модели данных.
Отображение модели, шаг первый
Многопроходная миграция не требует политик миграции настраиваемых сущностей, однако мы сделаем это, чтобы получить более подробную информацию для этого примера. Итак, мы добавляем к сущности настраиваемую политику. Это всегда подкласс NSEntityMigrationPolicy
.
Этот класс политики реализует некоторые методы для выполнения нашей миграции. Однако это просто в этом случае , поэтому мы должны реализовать только один метод: createDestinationInstancesForSourceInstance:entityMapping:manager:error:
.
Реализация будет выглядеть так:
StepOneEntityMigrationPolicy.m
#import "StepOneEntityMigrationPolicy.h"
@implementation StepOneEntityMigrationPolicy
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping
manager:(NSMigrationManager *)manager
error:(NSError **)error
{
NSManagedObject *newObject =
[NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
NSDate *date = [sInstance valueForKey:@"timeStamp"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
[dateFormatter release];
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}
Последний шаг: сама миграция
Я пропущу часть настройки второй модели сопоставления, которая почти идентична, только timeIntervalSince1970, используемый для преобразования NSDate в double.
Наконец, нам нужно запустить миграцию. Я пока пропущу шаблонный код. Если нужно, выложу сюда. Его можно найти в разделе «Настройка процесса миграции», это просто слияние первых двух примеров кода. Третья и последняя часть будет изменена следующим образом: вместо использования метода NSMappingModel
класса класса mappingModelFromBundles:forSourceModel:destinationModel:
мы будем использовать, initWithContentsOfURL:
потому что метод класса вернет только одну, возможно первую, найденную модель сопоставления в пакете.
Теперь у нас есть две модели сопоставления, которые можно использовать на каждом проходе цикла и отправить метод миграции диспетчеру миграции. Вот и все.
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
комплекте.
Необходимо указать целевое хранилище, которое не должно быть исходным хранилищем. После успешной миграции вы можете удалить старый и переименовать новый.
Я внес некоторые изменения в модель данных после создания моделей сопоставления, это привело к некоторым ошибкам совместимости, которые я мог решить только путем воссоздания моделей сопоставления.