Ich habe eine große Importaufgabe, die ich mit Kerndaten erledigen muss.
Angenommen, mein Kerndatenmodell sieht folgendermaßen aus:
Car
----
identifier
type
Ich rufe eine Liste mit JSON für Fahrzeuginformationen von meinem Server ab und möchte sie dann mit meinem Kerndatenobjekt synchronisieren. Car
Dies bedeutet:
Wenn es sich um ein neues Auto handelt -> erstellen Sie Car
aus den neuen Informationen ein neues Kerndatenobjekt .
Wenn das Auto bereits vorhanden ist -> aktualisieren Sie das Core Data- Car
Objekt.
Daher möchte ich diesen Import im Hintergrund durchführen, ohne die Benutzeroberfläche zu blockieren, und während der Verwendung wird eine Tabellenansicht für Autos gescrollt, in der alle Autos angezeigt werden.
Momentan mache ich so etwas:
// create background context
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[bgContext setParentContext:self.mainContext];
[bgContext performBlock:^{
NSArray *newCarsInfo = [self fetchNewCarInfoFromServer];
// import the new data to Core Data...
// I'm trying to do an efficient import here,
// with few fetches as I can, and in batches
for (... num of batches ...) {
// do batch import...
// save bg context in the end of each batch
[bgContext save:&error];
}
// when all import batches are over I call save on the main context
// save
NSError *error = nil;
[self.mainContext save:&error];
}];
Aber ich bin mir nicht sicher, ob ich hier das Richtige tue, zum Beispiel:
Ist es in Ordnung, dass ich benutze setParentContext
?
Ich habe einige Beispiele gesehen, die es so verwenden, aber ich habe andere Beispiele gesehen, die nicht aufrufen setParentContext
, stattdessen machen sie so etwas:
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.persistentStoreCoordinator = self.mainContext.persistentStoreCoordinator;
bgContext.undoManager = nil;
Eine andere Sache, bei der ich mir nicht sicher bin, ist, wann ich save im Hauptkontext aufrufen soll. In meinem Beispiel rufe ich save am Ende des Imports auf, aber ich habe Beispiele gesehen, die Folgendes verwenden:
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
NSManagedObjectContext *moc = self.managedObjectContext;
if (note.object != moc) {
[moc performBlock:^(){
[moc mergeChangesFromContextDidSaveNotification:note];
}];
}
}];
Wie ich bereits erwähnt habe, möchte ich, dass der Benutzer während der Aktualisierung mit den Daten interagieren kann. Was ist also sicher, wenn der Benutzer einen Fahrzeugtyp ändert, während der Import dasselbe Fahrzeug ändert?
AKTUALISIEREN:
Dank der großartigen Erklärung von @TheBasicMind versuche ich, Option A zu implementieren, sodass mein Code ungefähr so aussieht:
Dies ist die Kerndatenkonfiguration in AppDelegate:
AppDelegate.m
#pragma mark - Core Data stack
- (void)saveContext {
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
DDLogError(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}
// main
- (NSManagedObjectContext *)managedObjectContext {
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.parentContext = [self saveManagedObjectContext];
return _managedObjectContext;
}
// save context, parent of main context
- (NSManagedObjectContext *)saveManagedObjectContext {
if (_writerManagedObjectContext != nil) {
return _writerManagedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
_writerManagedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_writerManagedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _writerManagedObjectContext;
}
Und so sieht meine Importmethode jetzt aus:
- (void)import {
NSManagedObjectContext *saveObjectContext = [AppDelegate saveManagedObjectContext];
// create background context
NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc]initWithConcurrencyType:NSPrivateQueueConcurrencyType];
bgContext.parentContext = saveObjectContext;
[bgContext performBlock:^{
NSArray *newCarsInfo = [self fetchNewCarInfoFromServer];
// import the new data to Core Data...
// I'm trying to do an efficient import here,
// with few fetches as I can, and in batches
for (... num of batches ...) {
// do batch import...
// save bg context in the end of each batch
[bgContext save:&error];
}
// no call here for main save...
// instead use NSManagedObjectContextDidSaveNotification to merge changes
}];
}
Und ich habe auch folgenden Beobachter:
[[NSNotificationCenter defaultCenter] addObserverForName:NSManagedObjectContextDidSaveNotification object:nil queue:nil usingBlock:^(NSNotification* note) {
NSManagedObjectContext *mainContext = self.managedObjectContext;
NSManagedObjectContext *otherMoc = note.object;
if (otherMoc.persistentStoreCoordinator == mainContext.persistentStoreCoordinator) {
if (otherMoc != mainContext) {
[mainContext performBlock:^(){
[mainContext mergeChangesFromContextDidSaveNotification:note];
}];
}
}
}];