Od jakiegoś czasu borykam się z tym samym problemem. Dyskusja na ten temat dała mi do tej pory kilka pomysłów, którymi podzielę się teraz.
Należy pamiętać, że jest to w zasadzie niesprawdzone, ponieważ w moim przypadku bardzo rzadko zdarza się to tylko podczas testu i nie ma dla mnie łatwego sposobu na odtworzenie tego.
Mam taką samą konfigurację stosu CoreData - Główny MOC w prywatnej kolejce, która ma dziecko w głównej kolejce i jest używana jako główny kontekst aplikacji. Ostatecznie, operacje importu zbiorczego (find-or-create) są przekazywane do trzeciego MOC-a za pomocą kolejki tła. Po zakończeniu operacji zapisywane są dane do PSC.
Przeniosłem cały stos danych podstawowych z AppDelegate do oddzielnej klasy (AppModel
), która zapewnia aplikacji dostęp do zagregowanego obiektu głównego domeny (Player
), a także funkcję pomocniczą do wykonywania operacji w tle na modelu (performBlock:onSuccess:onError:
).
Na szczęście dla wszystkich głównych operacji CoreData są przekazywane przez tę metodę, więc jeśli mogę zapewnić, że te operacje są uruchamiane seryjnie, powielony problem powinien zostać rozwiązany.
- (void) performBlock: (void(^)(Player *player, NSManagedObjectContext *managedObjectContext)) operation onSuccess: (void(^)()) successCallback onError:(void(^)(id error)) errorCallback
{
//Add this operation to the NSOperationQueue to ensure that
//duplicate records are not created in a multi-threaded environment
[self.operationQueue addOperationWithBlock:^{
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[managedObjectContext setUndoManager:nil];
[managedObjectContext setParentContext:self.mainManagedObjectContext];
[managedObjectContext performBlockAndWait:^{
//Retrive a copy of the Player object attached to the new context
id player = [managedObjectContext objectWithID:[self.player objectID]];
//Execute the block operation
operation(player, managedObjectContext);
NSError *error = nil;
if (![managedObjectContext save:&error])
{
//Call the error handler
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", error);
if(errorCallback) return errorCallback(error);
});
return;
}
//Save the parent MOC (mainManagedObjectContext) - WILL BLOCK MAIN THREAD BREIFLY
[managedObjectContext.parentContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext.parentContext save:&error])
{
//Call the error handler
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", error);
if(errorCallback) return errorCallback(error);
});
return;
}
}];
//Attempt to clear any retain cycles created during operation
[managedObjectContext reset];
//Call the success handler
dispatch_async(dispatch_get_main_queue(), ^{
if (successCallback) return successCallback();
});
}];
}];
}
Co ja tu dodać, że mam nadzieję, że ma zamiar rozwiązać ten problem dla mnie jest owijanie całość w addOperationWithBlock
. Moja kolejka operacja jest po prostu skonfigurowany w następujący sposób:
single.operationQueue = [[NSOperationQueue alloc] init];
[single.operationQueue setMaxConcurrentOperationCount:1];
W mojej klasie API, mogę wykonać import na mojej pracy w następujący sposób:
- (void) importUpdates: (id) methodResult onSuccess: (void (^)()) successCallback onError: (void (^)(id error)) errorCallback
{
[_model performBlock:^(Player *player, NSManagedObjectContext *managedObjectContext) {
//Perform bulk import for data in methodResult using the provided managedObjectContext
} onSuccess:^{
//Call the success handler
dispatch_async(dispatch_get_main_queue(), ^{
if (successCallback) return successCallback();
});
} onError:errorCallback];
}
Teraz z NSOperationQueue
w miejscu powinno być dłużej możliwe, aby więcej niż jedna operacja wsadowa miała miejsce w tym samym czasie.
Doskonałe pytanie. Mam taki sam problem i już miałem zadać to pytanie. – djskinner