Implementieren eines schnellen und effizienten Imports von Kerndaten unter iOS 5

Frage: Wie kann ich erreichen, dass in meinem untergeordneten Kontext Änderungen im übergeordneten Kontext beibehalten werden, sodass mein NSFetchedResultsController zum Aktualisieren der Benutzeroberfläche veranlasst wird?

Hier ist das Setup:

Sie haben eine App, die viele XML-Daten herunterlädt und hinzufügt (ungefähr 2 Millionen Datensätze, jeder ungefähr so ​​groß wie ein normaler Textabschnitt). Die SQLite-Datei hat eine Größe von ungefähr 500 MB. Das Hinzufügen dieses Inhalts zu Core Data erfordert Zeit, aber Sie möchten, dass der Benutzer die App verwenden kann, während die Daten inkrementell in den Datenspeicher geladen werden. Es muss für den Benutzer unsichtbar und nicht wahrnehmbar sein, dass große Datenmengen verschoben werden, also keine Hänge, keine Unruhe: Schriftrollen wie Butter. Die App ist jedoch umso nützlicher, je mehr Daten hinzugefügt werden, sodass wir nicht ewig warten können, bis die Daten zum Core Data Store hinzugefügt werden. In Code bedeutet dies, dass ich Code wie diesen im Importcode unbedingt vermeiden möchte:

<code>[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
</code>

Die App ist nur für iOS 5 ausgelegt. Das langsamste Gerät, das es unterstützen muss, ist ein iPhone 3GS.

Hier sind die Ressourcen, die ich bisher verwendet habe, um meine aktuelle Lösung zu entwickeln:

Apple Core Data Programming Guide: Effizientes Importieren von Daten

Verwenden Sie Autorelease-Pools, um den Speicher niedrig zu haltenKosten für Beziehungen. Importiere es flach und repariere dann die Beziehungen am EndeFragen Sie nicht, ob Sie helfen können, es verlangsamt die Dinge auf eine O (n ^ 2) WeiseIn Chargen importieren: Speichern, Zurücksetzen, Entleeren und wiederholenSchalten Sie den Undo Manager beim Import aus

iDeveloper TV - Kerndatenleistung

Verwenden Sie drei Kontexte: Master, Main und Confinement

iDeveloper TV - Kerndaten für Mac, iPhone & iPad Update

Wenn Sie mit performBlock das Speichern in anderen Warteschlangen ausführen, geht es schnell.Die Verschlüsselung verlangsamt die Arbeit und schaltet sie aus, wenn Sie können.

Importieren und Anzeigen großer Datenmengen in Kerndaten von Marcus Zarra

Sie können den Import verlangsamen, indem Sie der aktuellen Ausführungsschleife Zeit geben, damit sich die Dinge für den Benutzer reibungslos anfühlen.Der Beispielcode zeigt, dass es möglich ist, umfangreiche Importe durchzuführen und die Benutzeroberfläche ansprechend zu halten, jedoch nicht so schnell wie in 3 Kontexten und asynchron auf der Festplatte zu speichern.Meine aktuelle Lösung

Ich habe 3 Instanzen von NSManagedObjectContext:

masterManagedObjectContext - Dies ist der Kontext mit dem NSPersistentStoreCoordinator, der für das Speichern auf der Festplatte verantwortlich ist. Ich mache das, damit meine Speicherungen asynchron und daher sehr schnell sind. Ich erstelle es beim Start so:

<code>masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
</code>

mainManagedObjectContext - Dies ist der Kontext, den die Benutzeroberfläche überall verwendet. Es ist ein untergeordnetes Element von masterManagedObjectContext. Ich erstelle es so:

<code>mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
</code>

backgroundContext - Dieser Kontext wird in meiner NSOperation-Unterklasse erstellt, die für den Import der XML-Daten in Core Data verantwortlich ist. Ich erstelle es in der Hauptmethode der Operation und verknüpfe es dort mit dem Masterkontext.

<code>backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
</code>

Das funktioniert tatsächlich sehr, sehr schnell. Allein durch dieses 3-Kontext-Setup konnte ich meine Importgeschwindigkeit um über das 10-fache steigern! Ehrlich gesagt ist das schwer zu glauben. (Dieses grundlegende Design sollte Teil der Standard-Core-Data-Vorlage sein ...)

Während des Importvorgangs speichere ich 2 verschiedene Wege. Alle 1000 Elemente, die ich im Hintergrundkontext speichere:

<code>BOOL saveSuccess = [backgroundContext save:&error];
</code>

Am Ende des Importvorgangs speichere ich dann den Master / Parent-Kontext, wodurch angeblich Änderungen an den anderen untergeordneten Kontexten vorgenommen werden, einschließlich des Hauptkontexts:

<code>[masterManagedObjectContext performBlock:^{
   NSError *parentContextError = nil;
   BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
</code>

Problem: Das Problem ist, dass meine Benutzeroberfläche nicht aktualisiert wird, bis ich die Ansicht neu lade.

Ich habe einen einfachen UIViewController mit einer UITableView, die Daten mit einem NSFetchedResultsController eingespeist wird. Nach Abschluss des Importvorgangs werden dem NSFetchedResultsController keine Änderungen im übergeordneten / übergeordneten Kontext angezeigt, sodass die Benutzeroberfläche nicht automatisch aktualisiert wird, wie ich es gewohnt bin. Wenn ich den UIViewController vom Stack lösche und wieder lade, sind alle Daten da.

Frage: Wie kann ich erreichen, dass in meinem untergeordneten Kontext Änderungen im übergeordneten Kontext beibehalten werden, sodass mein NSFetchedResultsController zum Aktualisieren der Benutzeroberfläche veranlasst wird?

Ich habe folgendes ausprobiert, bei dem die App gerade hängt:

<code>- (void)saveMasterContext {
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];    
    [notificationCenter addObserver:self selector:@selector(contextChanged:) name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];

    NSError *error = nil;
    BOOL saveSuccess = [masterManagedObjectContext save:&error];

    [notificationCenter removeObserver:self name:NSManagedObjectContextDidSaveNotification object:masterManagedObjectContext];
}

- (void)contextChanged:(NSNotification*)notification
{
    if ([notification object] == mainManagedObjectContext) return;

    if (![NSThread isMainThread]) {
        [self performSelectorOnMainThread:@selector(contextChanged:) withObject:notification waitUntilDone:YES];
        return;
    }

    [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
</code>

Antworten auf die Frage(1)

Ihre Antwort auf die Frage