5 votes

Concurrence NSPersistentContainer pour la sauvegarde des données de base

J'ai lu quelques blogs à ce sujet mais je ne sais toujours pas comment utiliser NSPersistentContainer. performBackgroundTask pour créer une entité et l'enregistrer. Après avoir créé une instance en appelant la méthode de commodité init(context moc: NSManagedObjectContext) en performBackgroundTask() { (moc) in } si je vérifie container.viewContext.hasChanges cela renvoie false et dit qu'il n'y a rien à sauvegarder, si j'appelle save on moc (MOC d'arrière-plan créé pour ce bloc) Je reçois des erreurs comme celle-ci :

fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
    "NSMergeConflict (0x17466c500) for NSManagedObject (0x1702cd3c0) with objectID '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ... }fatal error: Failure to save context: Error Domain=NSCocoaErrorDomain Code=133020 "Could not merge changes." UserInfo={conflictList=(
    "NSMergeConflict (0x170664b80) for NSManagedObject (0x1742cb980) with objectID '0xd000000000100000 <x-coredata://3EE6E11B-1901-47B5-9931-3C95D6513974/Currency/p4>' with oldVersion = 1 and newVersion = 2 and old cached row = {id = 2; ...} and new database row = {id = 2; ...}"
)}

Je n'ai donc pas réussi à faire fonctionner la concurrence et j'apprécierais vraiment que quelqu'un m'explique la manière correcte d'utiliser cette fonctionnalité sur les données de base dans iOS 10.

7voto

Jon Rose Points 4849

TL:DR : Votre problème est que vous écrivez en utilisant à la fois la fonction viewContext et avec des contextes de fond. Vous ne devez écrire dans les données de base que d'une seule manière synchrone.

Explication complète : Si un objet est modifié en même temps à partir de deux contextes différents, core-data ne sait pas quoi faire. Vous pouvez définir une mergePolicy pour déterminer quelle modification doit l'emporter, mais ce n'est pas vraiment une bonne solution, car vous pouvez perdre des données de cette façon. La façon dont beaucoup de professionnels ont traité le problème depuis longtemps était d'avoir une file d'attente d'opérations pour mettre en file d'attente les écritures afin qu'il n'y ait qu'une seule écriture en cours à la fois, et d'avoir un autre contexte sur le thread principal uniquement pour les lectures. De cette façon, il n'y a jamais de conflits de fusion. (voir https://vimeo.com/89370886 pour une excellente explication sur cette configuration).

Faire cette installation avec NSPersistentContainer est très facile. Dans votre gestionnaire de données de base, créez une NSOperationQueue.

_persistentContainerQueue = [[NSOperationQueue alloc] init];
_persistentContainerQueue.maxConcurrentOperationCount = 1;

Et faire toute l'écriture en utilisant cette file d'attente :

- (void)enqueueCoreDataBlock:(void (^)(NSManagedObjectContext* context))block{
    void (^blockCopy)(NSManagedObjectContext*) = [block copy];

    [self.persistentContainerQueue addOperation:[NSBlockOperation blockOperationWithBlock:^{
        NSManagedObjectContext* context =  self.persistentContainer.newBackgroundContext;
        [context performBlockAndWait:^{
            blockCopy(context);
            [context save:NULL];  //Don't just pass NULL here. look at the error and log it to your analytics service
        }];
    }]];
}

Lorsque vous appelez enqueueCoreDataBlock le bloc est mis en file d'attente pour s'assurer qu'il n'y a pas de conflits de fusion. Mais si vous écrivez dans le bloc viewContext qui annulerait cette installation. De la même manière, vous devez traiter tous les autres contextes que vous créez (avec newBackgroundContext ou avec performBackgroundTask ) comme étant en lecture seule car ils seront également en dehors de la file d'attente d'écriture.

Au début, je pensais que NSPersistentContainer 's performBackgroundTask avait une file d'attente interne, ce que les premiers tests ont confirmé. Après d'autres tests, j'ai vu que cela pouvait également conduire à des conflits de fusion.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X