UIManagedDocument avec NSFetchedResultsController et context d'arrière-plan

J'essaie de faire fonctionner les choses suivantes.

J'ai une vue de table qui affiche des données extraites d'une API dans une vue de table. Pour ce faire, j'utilise un NSFetchedResultsController:

self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.database.managedObjectContext sectionNameKeyPath:nil cacheName:nil]; 

Je crée mes entités dans un context d'arrière-plan comme celui-ci:

  NSManagedObjectContext *backgroundContext; backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; backgroundContext.parentContext = document.managedObjectContext; [backgroundContext performBlock:^{ [MyAPI createEntitiesInContext:backgroundContext]; NSError *error = nil; [backgroundContext save:&error]; if (error) NSLog(@"error: %@",error.localizedDescription); [document.managedObjectContext performBlock:^{ [document updateChangeCount:UIDocumentChangeDone]; [document.managedObjectContext save:nil]; }]; 

Maintenant, chaque fois que je reçois de nouveldatatables (et insert / mettre à jour des entités comme montré ci-dessus), mon NSFetchedResultsController ne fonctionne pas comme il se doit. En particulier, je suis toujours en train de mettre à jour une entité (sans en créer une nouvelle), mais ma vue de table montre deux entités. Une fois que je redémarre l'application, elle s'affiche correctement.

Si j'effectue la création des entités ([MyAPI createEntities]) dans self.database.managedObjectContext, tout fonctionne correctement.

Une idée de ce que je fais mal? En regardant à travers les discussions existantes sur SO, je pense que je le fais de la bonne façon. Encore une fois, si je ne fais pas les sauvegardes des données de base dans le context de l'arrière-plan (mais sur document.managedObjectContext) alors ça marche bien …

J'ai lu sur un problème similaire sur les forums de développement Apple aujourd'hui. Peut-être que c'est le même problème que le vôtre, https://devforums.apple.com/message/666492#666492 , auquel cas il y a peut-être un bug (ou au less quelqu'un d'autre avec le même problème pour en discuter!).

En supposant que ce ne soit pas, il semble que ce que vous voulez faire devrait être parfaitement possible avec des contexts nesteds, et donc en supposant qu'aucun bogue avec UIManagedDocument .

Ma seule réserve est que j'ai essayé d'get le chargement par lots en utilisant UIManagedDocument et il semble que cela ne fonctionne pas avec les contexts nesteds ( https://stackoverflow.com/q/11274412/1347502 ). Je pense que l'un des principaux avantages de NSFetchedResultsController est sa capacité à améliorer les performances grâce au chargement par lots. Donc, si cela ne peut pas être fait dans UIManagedDocument NSFetchedResultsController n'est peut-être pas prêt à être utilisé avec UIManagedDocument mais je n'ai pas encore atteint le fond de ce problème.

À part cette réserve, la plupart des instructions que j'ai lues ou vues sur les contexts nesteds et le travail de fond semblent se faire avec des contexts d'enfants pairs . Ce que vous avez décrit est une configuration parent, enfant, petit-enfant. Dans la video de la WWDC 2012 "Session 214 – Meilleures pratiques pour datatables de base" (+ 16:00 minutes), Apple recommand d'append un context de pairs au context parent pour ce scénario, par exemple

 backgroundContext.parentContext = document.managedObjectContext.parentContext; 

Le travail est effectué de manière asynchronous dans ce context, puis poussé vers le parent via un appel à save sur le context d'arrière-plan. Le parent serait alors sauvegardé de manière asynchronous et tout context homologue, en l'occurrence document.managedObjectContext , accèderait aux modifications via une extraction, une fusion ou une actualisation. Ceci est également décrit dans la documentation UIManagedDocument :

  • Le cas échéant, vous pouvez charger des données à partir d'un thread d'arrière-plan directement dans le context parent. Vous pouvez get le context parent en utilisant parentContext. Le chargement de données dans le context parent signifie que vous ne perturbez pas les opérations du context enfant. Vous pouvez récupérer datatables chargées en arrière-plan simplement en exécutant une extraction.

[Edit: relisant ceci, il pourrait simplement reorder la suggestion de Jeffery, c'est-à-dire ne pas créer de nouveaux contexts et simplement utiliser le context parent.]

Cela étant dit, la documentation suggère également que, généralement, vous n'appelez pas save sur les contexts enfants mais utilisez les methods de sauvegarde de UIManagedDocument . Cela peut être une occasion où vous appelez save ou peut-être une partie du problème. Appeler sauver sur le context parent est plus fortement découragé, comme mentionné par Jeffery. Une autre réponse que j'ai lue au débordement de la stack recommand d'utiliser updateChangeCount pour triggersr les sauvegardes de UIManagedDocument . Mais je n'ai rien lu d'Apple, donc peut-être dans ce cas appeler la UIManagedDocument saveToURL:forSaveOperation:completionHandler: serait approprié pour tout synchroniser et sauvegarder.

Je suppose que le prochain problème évident est de savoir comment notifier NSFetchedResultsController que des modifications ont eu lieu. Je serais tenté de simplifier la configuration comme indiqué ci-dessus, puis de vous abonner aux diverses notifications NSManagedObjectContextObjectsDidChangeNotification ou de les save sur les différents contexts et de voir lesquels, le cas échéant, sont appelés lorsque UIMangedDocument enregistre, sauvegarde automatiquement ou lorsque les modifications d'arrière-plan sont enregistrées dans le parent. en supposant que cela soit permis dans ce cas). Je suppose que le NSFetchedResultsController est câblé à ces notifications afin de restr en phase avec datatables sous-jacentes.

Alternativement peut-être vous avez besoin d'effectuer manuellement une extraction, de merge, ou d'actualiser dans le context principal pour get les modifications tirées à travers, puis en quelque sorte notifier NSFetchedResultsController qu'il doit actualiser?

Personnellement, je me request si UIManagedDocument est prêt pour la consommation générale, il n'y avait aucune mention à WWDC cette année et à la place une longue discussion sur la façon de build une solution beaucoup plus compliquée a été présentée: "Session 227 – Utiliser iCloud avec Core Data"

Dans ma méthode où j'obtiens des données du server, je crée d'abord les entités et après que j'appelle ces deux methods pour save les changements au document:

 [self.managedObjectContext performBlock:^{ // create my entities [self.document updateChangeCount:UIDocumentChangeDone]; [self.document savePresentedItemChangesWithCompletionHandler:^(NSError *errorOrNil) { ... }]; }]; 

Parce que vous mettez à jour les résultats dans un context différent, je pense que vous devrez appeler [self.fetchedResultsController performFetch:&error] dans la command controllers -viewWillAppear: method.


Après les mises à jour

OK, vous ne devriez pas appeler [backgroundContext save:&error] ou [document.managedObjectContext save:nil] . Voir: Référence de la class UIManagedDocument

Vous devez généralement utiliser les methods UIDocument standard pour save le document. Si vous enregistrez directement le context enfant, vous ne validez que le context parent et non le magasin de documents. Si vous enregistrez le context parent directement, vous évitez d'autres opérations importantes effectuées par le document.

J'ai dû utiliser -insertedObjects et obtainPermanentIDsForObjects:error: pour persister de nouveaux objects créés dans un context.

Ensuite, je ne pense pas que vous ayez besoin de créer un nouveau context pour fonctionner en arrière-plan. document.managedObjectContext.parentContext doit être un context d'arrière-plan disponible pour exécuter des mises à jour.

Enfin, je n'appelle pas [document updateChangeCount:UIDocumentChangeDone] très souvent. Ceci est pris en charge par le document automatiquement. Vous pouvez toujours le faire quand vous le voulez, mais cela ne devrait pas être nécessaire.

Voici comment j'appellerais votre méthode -createEntitiesInContext .

 [document.managedObjectContext.parentContext performBlock:^{ [MyAPI createEntitiesInContext:document.managedObjectContext.parentContext]; NSSet *objects = [document.managedObjectContext.parentContext insertedObjects]; if (objects.count > 0) { NSError *error = nil; [document.managedObjectContext.parentContext obtainPermanentIDsForObjects:objects error:&error] if (error) NSLog(@"error: %@",error.localizedDescription); } }];