I am trying to get the following working.
I have a table view that is displaying data fetched from an API in a table view. For that purpose I am using a NSFetchedResultsController:
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:self.database.managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
I create my entities in a background context like this:
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];
}];
Now, whenever I get new data (and insert/update entities like shown just above), my NSFetchedResultsController doesn't quite work as it should. In particular, I am always updating one entity (not creating a new one), but my table view shows two entities. Once I restart the app it shows up correctly.
If I perform the creation of the entities ([MyAPI createEntities]) in self.database.managedObjectContext, everything works fine.
Any idea what I am doing wrong? Looking through the existing threads here on SO makes me think that I'm doing it the right way. Again, if I do not do the core data saves in the background context (but on document.managedObjectContext) then it works fine...
In my method where I fetch data from server, I first create the Entities and after that I call these two methods to save the changes to the document :
I read about a similar problem on the Apple dev forums today. Perhaps this is the same problem as yours, https://devforums.apple.com/message/666492#666492, in which case perhaps there is a bug (or at least someone else with the same issue to discuss it with!).
Assuming it isn't, it sounds like what you want to do should be perfectly possible with nested contexts, and therefore assuming no bugs with
UIManagedDocument
.My only reservation is that I've been trying to get batch loading working with
UIManagedDocument
and it seems like it does not work with nested contexts (https://stackoverflow.com/q/11274412/1347502). I would think one of the main benefits ofNSFetchedResultsController
is it's ability to improve performance through batch loading. So if this can't be done inUIManagedDocument
perhapsNSFetchedResultsController
isn't ready for use withUIManagedDocument
but I haven't got to the bottom of that issue yet.That reservation aside, most of the instruction I've read or viewed about nested contexts and background work seems to be done with peer child contexts. What you have described is a parent, child, grandchild configuration. In the WWDC 2012 video "Session 214 - Core Data Best Practices" (+ 16:00 minutes) Apple recommend adding another peer context to the parent context for this scenario, e.g
The work is performed asynchronously in this context and then pushed up to the parent via a call to save on the background context. The parent would then be saved asynchronously and any peer contexts, in this case the
document.managedObjectContext
, would access the changes via a fetch, merge, or refresh. This is also described in theUIManagedDocument
documentation:[Edit: re-reading this it could just be recommending Jeffery's suggestion i.e. not creating any new contexts at all and just using the parent context.]
That being said the documentation also suggests that typically you do not call save on child contexts but use the
UIManagedDocument
's save methods. This may be an occasion when you do call save or perhaps part of the problem. Calling save on the parent context is more strongly discouraged, as mentioned by Jeffery. Another answer I've read on stack overflow recommended only usingupdateChangeCount
to triggerUIManagedDocument
saves. But I've not read any thing from Apple, so perhaps in this case a to call theUIManagedDocument saveToURL:forSaveOperation:completionHandler:
method would be appropriate to get everything in sync and saved.I guess the next obvious issue is how to notify NSFetchedResultsController that changes have occurred. I would be tempted to simplify the setup as discussed above and then subscribe to the various
NSManagedObjectContextObjectsDidChangeNotification
or save notifications on the different contexts and see which, if any, are called whenUIMangedDocument
saves, autosaves, or when background changes are saved to the parent (assuming that is allowable in this case). I assume theNSFetchedResultsController
is wired to these notifications in order to keep in sync with the underlying data.Alternatively perhaps you need to manually perform a fetch, merge, or refresh in the main context to get the changes pulled through and then somehow notify
NSFetchedResultsController
that it needs to refresh?Personally I'm wondering if
UIManagedDocument
is ready for general consumption, there was no mention of it at WWDC this year and instead a lengthy discussion of how to build a much more complicated solution was presented: "Session 227 - Using iCloud with Core Data"Because you are updating the results on a different context, I think you will need to call
[self.fetchedResultsController performFetch:&error]
in your view controllers-viewWillAppear:
method.After Updates
OK, you should not be calling
[backgroundContext save:&error]
or[document.managedObjectContext save:nil]
. See: UIManagedDocument Class ReferenceI had to use
-insertedObjects
andobtainPermanentIDsForObjects:error:
to persist new objects created in a context.Next, I don't think you need to create a new context to run in the background.
document.managedObjectContext.parentContext
should be an available background context to run updates in.Finally, I don't call
[document updateChangeCount:UIDocumentChangeDone]
very often. This is taken care of by the document automatically. You can still do it any time you want, but it shouldn't be necessary.Here is how I would call Your
-createEntitiesInContext
method.