Beispiel oder Erklärung der Kerndatenmigration mit mehreren Durchgängen?

85

Meine iPhone-App muss ihren Kerndatenspeicher migrieren, und einige der Datenbanken sind ziemlich groß. In der Dokumentation von Apple wird vorgeschlagen, "mehrere Durchgänge" zum Migrieren von Daten zu verwenden, um die Speichernutzung zu reduzieren. Die Dokumentation ist jedoch sehr begrenzt und erklärt nicht sehr gut, wie dies tatsächlich zu tun ist. Kann mich jemand auf ein gutes Beispiel hinweisen oder detailliert erklären, wie man das tatsächlich schafft?

Jason
quelle
Haben Sie tatsächlich Speicherprobleme? Ist Ihre Migration leichtgewichtig oder möchten Sie einen NSMigrationManager verwenden?
Nick Weaver
Ja, die GDB-Konsole zeigte an, dass Speicherwarnungen vorhanden waren, und die App stürzt dann aufgrund des begrenzten Speichers ab. Ich habe sowohl Lightweight Migration als auch NSMigrationManager ausprobiert, aber im Moment versuche ich, NSMigrationManager zu verwenden.
Jason
ok, kannst du etwas genauer ins Detail gehen, was sich geändert hat?
Nick Weaver
Schließlich habe ich herausgefunden, meine Antwort zu lesen.
Nick Weaver
Hallo Jason, könntest du das in der Frage beheben?
Yuchen Zhong

Antworten:

174

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

Geben Sie hier die Bildbeschreibung ein Geben Sie hier die Bildbeschreibung ein

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:

Geben Sie hier die Bildbeschreibung ein

Datenmodell Version 2

Der Unterschied ist: Die Event-Entität ist weg und wir haben zwei neue. Eine, in der ein Zeitstempel als doublegespeichert 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 Geben Sie hier die Bildbeschreibung ein

2. Wählen Sie einen Namen, ich habe StepOne gewählt

3. Legen Sie das Quell- und Zieldatenmodell fest

Geben Sie hier die Bildbeschreibung ein

Abbildung Modell Schritt Eins

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

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.

Geben Sie hier die Bildbeschreibung ein

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 NSMappingModelKlasse 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 cdmBundle.

  • 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.

Nick Weaver
quelle
59
Verdammt, das ist kompliziert. Was dachte Apple?
aroth
7
Ich weiß es nicht, aber wenn ich denke, dass Kerndaten eine gute Idee sind, bemühe ich mich, eine einfachere und wartbarere Lösung zu finden.
Nick Weaver
5
Vielen Dank! Dies ist eine hervorragende Antwort. Es scheint kompliziert, aber es ist nicht so schlimm, wenn Sie die Schritte gelernt haben. Das größte Problem ist, dass die Dokumentation es für Sie nicht so formuliert.
Bentford
2
Hier ist der aktualisierte Link zum Anpassen des Migrationsprozesses. Es hat sich bewegt, seit dieser Beitrag geschrieben wurde. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430
@NickWeaver Wie bestimmen Sie destinationStoreURL? Erstellen Sie es oder wird es während des Migrationsprozesses vom Kerndatensystem erstellt?
dev gr
3

Diese Fragen hängen zusammen:

Speicherprobleme beim Migrieren großer CoreData-Datenspeicher auf dem iPhone

Migration von Core-Daten mit mehreren Durchläufen in Chunks mit iOS

Um den ersten Link zu zitieren:

Dies wird in der offiziellen Dokumentation im Abschnitt "Mehrere Durchgänge" erläutert. Es wird jedoch anscheinend empfohlen, Ihre Migration nach Entitätstypen aufzuteilen, dh mehrere Zuordnungsmodelle zu erstellen, von denen jedes eine Teilmenge der Entitätstypen aus dem migriert vollständiges Datenmodell.

Occulus
quelle
1
Danke für die Links. Das Problem ist, dass niemand im Detail erklärt, wie man es in mehreren Durchgängen einrichtet. Wie sollte ich mehrere Mapping-Modelle einrichten, damit es effektiv funktioniert?
Jason
-5

Angenommen, Ihr Datenbankschema enthält 5 Entitäten, z. B. Person, Schüler, Kurs, Klasse und Registrierung, um das Standardbeispiel zu verwenden, bei dem der Schüler die Unterklassen Person, Klasse implementiert Kurs und Registrierung Klasse und Schüler verbindet. Wenn Sie Änderungen an all diesen Tabellendefinitionen vorgenommen haben, müssen Sie bei den Basisklassen beginnen und sich nach oben arbeiten. Sie können also nicht mit der Konvertierung von Registrierungen beginnen, da jeder Registrierungsdatensatz davon abhängt, ob Klasse und Schüler dort sind. Sie beginnen also damit, nur die Personentabelle zu migrieren, vorhandene Zeilen in die neue Tabelle zu kopieren, die neuen Felder (falls möglich) auszufüllen und die entfernten Spalten zu verwerfen. Führen Sie jede Migration innerhalb eines Autorelease-Pools durch, damit Ihr Speicher wieder gestartet werden kann.

Sobald die Personentabelle fertig ist, können Sie die Schülertabelle konvertieren. Dann hüpfen Sie zu Kurs und dann Klasse und schließlich zur Registrierungstabelle.

Die andere Überlegung ist die Anzahl der Datensätze. Wenn wie Person tausend Zeilen hätte, müssten Sie etwa alle 100 das NSManagedObject-Äquivalent einer Version ausführen, um den Kontext des verwalteten Objekts mitzuteilen [moc refreshObject: ob mergeChanges: NEIN]; Stellen Sie außerdem Ihren veralteten Daten-Timer auf einen niedrigen Wert ein, damit der Speicher häufig geleert wird.

Papa Schlumpf
quelle
Schlagen Sie also im Wesentlichen vor, ein neues Kerndatenschema zu haben, das nicht Teil des alten Schemas ist, und die Daten von Hand in das neue Schema zu kopieren?
Jason
-1 Eine manuelle Zuordnung Ihrer Datenbank ist nicht erforderlich. Sie können bereitgestellte Datenbanken mithilfe einer einfachen Migration oder mit expliziten MappingModels migrieren.
Bentford