Dowiedziałem się, co podpowiada Apple w ich dokumentacji . W rzeczywistości jest to bardzo łatwe, ale długa droga, zanim stanie się to oczywiste. Zilustruję wyjaśnienie na przykładzie. Sytuacja początkowa jest następująca:
Wersja modelu danych 1
Jest to model, który otrzymujesz podczas tworzenia projektu za pomocą szablonu „aplikacja oparta na nawigacji z podstawowym magazynem danych”. Skompilowałem go i zrobiłem trochę mocnego uderzenia z pomocą pętli for, aby utworzyć około 2k wpisów, wszystkie z różnymi wartościami. Idziemy do 2.000 zdarzeń z wartością NSDate.
Teraz dodajemy drugą wersję modelu danych, która wygląda następująco:
Wersja modelu danych 2
Różnica polega na tym, że jednostka Event zniknęła, a mamy dwie nowe. Jeden, który przechowuje znacznik czasu jako a, double
a drugi, który powinien przechowywać datę jako NSString
.
Celem jest przeniesienie wszystkich zdarzeń wersji 1 do dwóch nowych jednostek i konwersja wartości podczas migracji. Powoduje to dwukrotność wartości, z których każda jest innego typu w oddzielnej jednostce.
Aby przeprowadzić migrację, wybieramy migrację ręcznie i robimy to z mapowaniem modeli. To także pierwsza część odpowiedzi na Twoje pytanie. Migrację wykonamy w dwóch krokach, ponieważ migracja 2k wpisów zajmuje dużo czasu, a my chcemy, aby zużycie pamięci było niskie.
Możesz nawet dalej podzielić te modele mapowania, aby migrować tylko zakresy jednostek. Powiedzmy, że mamy milion rekordów, to może zawiesić cały proces. Możliwe jest zawężenie pobieranych jednostek za pomocą predykatu filtru .
Wróćmy do naszych dwóch modeli mapowania.
Pierwszy model mapowania tworzymy w ten sposób:
1. Nowy plik -> Zasób -> Model mapowania
2. Wybierz nazwę, wybrałem StepOne
3. Ustaw źródłowy i docelowy model danych
Mapowanie modelu - krok pierwszy
Migracja wieloprzebiegowa nie wymaga niestandardowych zasad migracji encji, jednak zrobimy to, aby uzyskać więcej szczegółów w tym przykładzie. Więc dodajemy niestandardowe zasady do encji. To jest zawsze podklasa NSEntityMigrationPolicy
.
Ta klasa strategii implementuje pewne metody, aby dokonać migracji. Jednak to proste w tym przypadku tak będziemy musieli realizować tylko jedną metodę: createDestinationInstancesForSourceInstance:entityMapping:manager:error:
.
Implementacja będzie wyglądać następująco:
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;
}
Ostatni krok: sama migracja
Pominię część dotyczącą konfigurowania drugiego modelu mapowania, który jest prawie identyczny, tylko timeIntervalSince1970 użyty do konwersji NSDate na podwójną.
Wreszcie musimy uruchomić migrację. Na razie pominę kod standardowy. Jeśli potrzebujesz, napiszę tutaj. Można go znaleźć na stronie Dostosowywanie procesu migracji, to tylko połączenie dwóch pierwszych przykładów kodu. Trzecia i ostatnia część zostanie zmodyfikowana w następujący sposób: Zamiast używać metody NSMappingModel
klasy mappingModelFromBundles:forSourceModel:destinationModel:
, użyjemy metody, initWithContentsOfURL:
ponieważ metoda klasowa zwróci tylko jeden, być może pierwszy, znaleziony model mapowania w pakiecie.
Teraz mamy dwa modele mapowania, których można użyć w każdym przejściu pętli i wysłać metodę migracji do menedżera migracji. Otóż to.
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];
}
Uwagi
Model mapowania kończy się w cdm
pakiecie.
Należy podać magazyn docelowy i nie powinien to być magazyn źródłowy. Po udanej migracji możesz usunąć starą i zmienić nazwę nowej.
Dokonałem pewnych zmian w modelu danych po utworzeniu modeli mapujących, co spowodowało pewne błędy kompatybilności, które mogłem rozwiązać tylko odtwarzając modele mapowania.