UIManagedDocument with NSFetchedResultsController and background context

烈酒焚心 提交于 2019-12-06 15:56:32

问题


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...


回答1:


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 of NSFetchedResultsController is it's ability to improve performance through batch loading. So if this can't be done in UIManagedDocument perhaps NSFetchedResultsController isn't ready for use with UIManagedDocument 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

backgroundContext.parentContext = document.managedObjectContext.parentContext;

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 the UIManagedDocument documentation:

  • If appropriate, you can load data from a background thread directly to the parent context. You can get the parent context using parentContext. Loading data to the parent context means you do not perturb the child context’s operations. You can retrieve data loaded in the background simply by executing a fetch.

[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 using updateChangeCount to trigger UIManagedDocument saves. But I've not read any thing from Apple, so perhaps in this case a to call the UIManagedDocument 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 when UIMangedDocument saves, autosaves, or when background changes are saved to the parent (assuming that is allowable in this case). I assume the NSFetchedResultsController 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"




回答2:


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 :

[self.managedObjectContext performBlock:^{
     // create my entities


     [self.document updateChangeCount:UIDocumentChangeDone];
     [self.document savePresentedItemChangesWithCompletionHandler:^(NSError *errorOrNil) {
            ...
      }];
}];



回答3:


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 Reference

You should typically use the standard UIDocument methods to save the document. If you save the child context directly, you only commit changes to the parent context and not to the document store. If you save the parent context directly, you sidestep other important operations that the document performs.

I had to use -insertedObjects and obtainPermanentIDsForObjects: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.

[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);
    }
}];


来源:https://stackoverflow.com/questions/11350670/uimanageddocument-with-nsfetchedresultscontroller-and-background-context

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!