Frage : Wie kann ich in meinem untergeordneten Kontext feststellen, dass Änderungen im übergeordneten Kontext bestehen bleiben, sodass mein NSFetchedResultsController die Benutzeroberfläche aktualisiert?
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 wird ungefähr 500 MB groß. Das Hinzufügen dieses Inhalts zu Core Data benötigt Zeit, aber Sie möchten, dass der Benutzer die App verwenden kann, während die Daten schrittweise 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 Jitter: Schriftrollen wie Butter. Die App ist jedoch umso nützlicher, je mehr Daten hinzugefügt werden. Wir können also nicht ewig warten, bis die Daten zum Core Data Store hinzugefügt werden. Im Code bedeutet dies, dass ich Code wie diesen im Importcode wirklich vermeiden möchte:
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.25]];
Die App ist nur für iOS 5 verfügbar. Das langsamste Gerät, das unterstützt werden muss, ist ein iPhone 3GS.
Hier sind die Ressourcen, die ich bisher zur Entwicklung meiner aktuellen Lösung verwendet habe:
Apples Core Data Programming Guide: Effizientes Importieren von Daten
- Verwenden Sie Autorelease-Pools, um den Speicher niedrig zu halten
- Beziehungskosten. Importieren Sie flach und flicken Sie am Ende die Beziehungen
- Fragen Sie nicht, ob Sie helfen können, es verlangsamt die Dinge auf eine O (n ^ 2) Weise
- In Chargen importieren: speichern, zurücksetzen, entleeren und wiederholen
- Schalten Sie den Rückgängig-Manager beim Import aus
iDeveloper TV - Kerndatenleistung
- Verwenden Sie 3 Kontexte: Master-, Haupt- und Confinement-Kontexttypen
iDeveloper TV - Kerndaten für Mac, iPhone und iPad Update
- Das Ausführen von Speichern in anderen Warteschlangen mit performBlock beschleunigt die Arbeit.
- Die Verschlüsselung verlangsamt die Arbeit. Schalten Sie 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 der Benutzer reibungslos fühlt.
- Beispielcode beweist, dass es möglich ist, große Importe durchzuführen und die Benutzeroberfläche ansprechbar zu halten, jedoch nicht so schnell wie bei 3 Kontexten und asynchronem Speichern auf der Festplatte.
Meine aktuelle Lösung
Ich habe 3 Instanzen von NSManagedObjectContext:
masterManagedObjectContext - Dies ist der Kontext, der über den NSPersistentStoreCoordinator verfügt und für das Speichern auf der Festplatte verantwortlich ist. Ich mache das, damit meine Speicherungen asynchron und daher sehr schnell sein können. Ich erstelle es beim Start so:
masterManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[masterManagedObjectContext setPersistentStoreCoordinator:coordinator];
mainManagedObjectContext - Dies ist der Kontext, den die Benutzeroberfläche überall verwendet. Es ist ein untergeordnetes Element des masterManagedObjectContext. Ich erstelle es so:
mainManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[mainManagedObjectContext setUndoManager:nil];
[mainManagedObjectContext setParentContext:masterManagedObjectContext];
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 Master-Kontext.
backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
[backgroundContext setUndoManager:nil];
[backgroundContext setParentContext:masterManagedObjectContext];
Dies funktioniert tatsächlich sehr, sehr schnell. Durch dieses 3-Kontext-Setup konnte ich meine Importgeschwindigkeit um mehr als das 10-fache verbessern! Ehrlich gesagt ist das schwer zu glauben. (Dieses grundlegende Design sollte Teil der Standardvorlage für Kerndaten sein ...)
Während des Importvorgangs speichere ich 2 verschiedene Arten. Alle 1000 Elemente speichere ich im Hintergrundkontext:
BOOL saveSuccess = [backgroundContext save:&error];
Am Ende des Importvorgangs speichere ich dann den Master- / Elternkontext, der angeblich Änderungen an den anderen untergeordneten Kontexten einschließlich des Hauptkontexts überträgt:
[masterManagedObjectContext performBlock:^{
NSError *parentContextError = nil;
BOOL parentContextSaveSuccess = [masterManagedObjectContext save:&parentContextError];
}];
Problem : Das Problem ist, dass meine Benutzeroberfläche erst aktualisiert wird, wenn ich die Ansicht neu lade.
Ich habe einen einfachen UIViewController mit einem UITableView, dem Daten mit einem NSFetchedResultsController zugeführt werden. Wenn der Importvorgang abgeschlossen ist, werden im NSFetchedResultsController keine Änderungen gegenüber dem übergeordneten / Master-Kontext angezeigt, sodass die Benutzeroberfläche nicht automatisch aktualisiert wird, wie ich es gewohnt bin. Wenn ich den UIViewController vom Stapel nehme und erneut lade, sind alle Daten vorhanden.
Frage : Wie kann ich in meinem untergeordneten Kontext feststellen, dass Änderungen im übergeordneten Kontext bestehen bleiben, sodass mein NSFetchedResultsController die Benutzeroberfläche aktualisiert?
Ich habe folgendes versucht, das nur die App hängt:
- (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];
}