Ich habe herausgefunden, was Apple in seiner Dokumentation andeutet . Es ist eigentlich sehr einfach, aber noch ein langer Weg, bis es offensichtlich ist. Ich werde die Erklärung anhand eines Beispiels veranschaulichen. Die Ausgangssituation ist folgende:
Datenmodell Version 1
Dies ist das Modell, das Sie erhalten, wenn Sie ein Projekt mit der Vorlage "Navigationsbasierte App mit Kerndatenspeicher" erstellen. Ich habe es kompiliert und mit Hilfe einer for-Schleife hart getroffen, um ungefähr 2k Einträge mit unterschiedlichen Werten zu erstellen. Dort gehen wir 2.000 Ereignisse mit einem NSDate-Wert.
Jetzt fügen wir eine zweite Version des Datenmodells hinzu, die folgendermaßen aussieht:
Datenmodell Version 2
Der Unterschied ist: Die Event-Entität ist weg und wir haben zwei neue. Eine, in der ein Zeitstempel als double
gespeichert ist, und die zweite, in der ein Datum als gespeichert werden soll NSString
.
Ziel ist es, alle Ereignisse der Version 1 auf die beiden neuen Entitäten zu übertragen und die Werte während der Migration zu konvertieren. Dies führt zu doppelt so vielen Werten wie jeweils ein anderer Typ in einer separaten Entität.
Für die Migration wählen wir die Migration von Hand und dies mit Mapping-Modellen. Dies ist auch der erste Teil der Antwort auf Ihre Frage. Wir werden die Migration in zwei Schritten durchführen, da die Migration von 2k-Einträgen lange dauert und wir den Speicherbedarf gering halten möchten.
Sie können diese Zuordnungsmodelle sogar weiter aufteilen, um nur Bereiche der Entitäten zu migrieren. Angenommen, wir haben eine Million Datensätze. Dies kann den gesamten Prozess zum Absturz bringen. Es ist möglich, die abgerufenen Entitäten mit einem Filter-Prädikat einzugrenzen .
Zurück zu unseren beiden Mapping-Modellen.
Wir erstellen das erste Mapping-Modell wie folgt:
1. Neue Datei -> Ressource -> Zuordnungsmodell
2. Wählen Sie einen Namen, ich habe StepOne gewählt
3. Legen Sie das Quell- und Zieldatenmodell fest
Abbildung Modell Schritt Eins
Für die Multi-Pass-Migration sind keine benutzerdefinierten Entitätsmigrationsrichtlinien erforderlich. Wir werden dies jedoch tun, um in diesem Beispiel ein wenig mehr Details zu erhalten. Daher fügen wir der Entität eine benutzerdefinierte Richtlinie hinzu. Dies ist immer eine Unterklasse von NSEntityMigrationPolicy
.
Diese Richtlinienklasse implementiert einige Methoden, um unsere Migration durchzuführen. In diesem Fall ist es jedoch einfach, sodass wir nur eine Methode implementieren müssen : createDestinationInstancesForSourceInstance:entityMapping:manager:error:
.
Die Implementierung sieht folgendermaßen aus:
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;
}
Letzter Schritt: die Migration selbst
Ich überspringe den Teil zum Einrichten des zweiten Mapping-Modells, das fast identisch ist, nur ein timeIntervalSince1970, mit dem das NSDate in ein Double konvertiert wird.
Schließlich müssen wir die Migration auslösen. Ich werde den Boilerplate-Code vorerst überspringen. Wenn Sie es brauchen, werde ich hier posten. Sie finden es unter Anpassen des Migrationsprozesses. Es ist nur eine Zusammenführung der ersten beiden Codebeispiele. Der dritte und letzte Teil werden wie folgt geändert: Anstatt die Klassenmethode der NSMappingModel
Klasse zu verwenden, verwenden mappingModelFromBundles:forSourceModel:destinationModel:
wir die, initWithContentsOfURL:
da die Klassenmethode nur ein, möglicherweise das erste gefundene Zuordnungsmodell im Bundle zurückgibt.
Jetzt haben wir die beiden Zuordnungsmodelle, die in jedem Durchgang der Schleife verwendet werden können und die Migrationsmethode an den Migrationsmanager senden. Das ist es.
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];
}
Anmerkungen
Ein Mapping-Modell endet im cdm
Bundle.
Der Zielspeicher muss angegeben werden und sollte nicht der Quellspeicher sein. Sie können nach erfolgreicher Migration die alte löschen und die neue umbenennen.
Ich habe nach der Erstellung der Mapping-Modelle einige Änderungen am Datenmodell vorgenommen. Dies führte zu einigen Kompatibilitätsfehlern, die ich nur mit der Neuerstellung der Mapping-Modelle beheben konnte.