带有NSFetchedResultsController和背景上下文的UIManagedDocument

时间:2012-07-05 18:51:02

标签: objective-c ios core-data nsfetchedresultscontroller uimanageddocument

我正在努力让以下工作。

我有一个表视图,它在表视图中显示从API获取的数据。为此我使用的是NSFetchedResultsController:

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

我在背景上下文中创建我的实体,如下所示:

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

现在,每当我获得新数据(以及上面显示的插入/更新实体)时,我的NSFetchedResultsController就不能正常工作了。特别是,我总是更新一个实体(不创建新实体),但我的表视图显示了两个实体。一旦我重新启动应用程序,它就会正确显示。

如果我在self.database.managedObjectContext中执行实体的创建([MyAPI createEntities]),一切正常。

知道我做错了什么吗?通过现有的线索查看SO让我觉得我正在以正确的方式做到这一点。同样,如果我没有在后台上下文中保存核心数据(但是在document.managedObjectContext上),那么它可以正常工作......

3 个答案:

答案 0 :(得分:1)

我今天在Apple开发论坛上看到了类似的问题。也许这和你的问题一样https://devforums.apple.com/message/666492#666492,在这种情况下可能存在一个错误(或至少有其他人有同样的问题与之讨论!)。

假设它不是,听起来你应该完全可以使用嵌套的上下文,因此假设没有UIManagedDocument的错误。

我唯一的保留意见是我一直在努力让批量加载与UIManagedDocument一起使用,看起来它不适用于嵌套的上下文(https://stackoverflow.com/q/11274412/1347502)。我认为NSFetchedResultsController的一个主要好处是它能够通过批量加载来提高性能。因此,如果无法在UIManagedDocument中执行此操作,则NSFetchedResultsController可能尚未准备好与UIManagedDocument一起使用,但我还没有找到问题的根源。

除了这个保留之外,我阅读或查看的有关嵌套上下文和后台工作的大部分指令似乎都是通过 peer 子上下文完成的。您所描述的是父,子,孙配置。在WWDC 2012视频“会话214 - 核心数据最佳实践”(+ 16:00分钟)中,Apple建议在此方案的父上下文中添加另一个对等上下文,例如

backgroundContext.parentContext = document.managedObjectContext.parentContext;

工作在此上下文中异步执行,然后通过调用将其推送到父级以保存在背景上下文中。然后将异步保存父级,并且任何对等上下文(在本例中为document.managedObjectContext)将通过提取,合并或刷新来访问更改。 UIManagedDocument文档中也对此进行了描述:

  • 如果适用,您可以直接从后台线程加载数据 到父上下文。您可以使用获取父上下文 parentContext。将数据加载到父上下文意味着您没有 扰乱子环境的操作。您可以检索加载的数据 在后台只需执行一次提取。

[编辑:重读这可能只是推荐Jeffery的建议,即根本不创建任何新的上下文,只使用父上下文。]

据说文档还建议您通常不要在子上下文中调用save,而是使用UIManagedDocument的保存方法。这可能是您调用保存或可能是部分问题的情况。正如Jeffery所提到的,更强烈地不鼓励在父上下文中调用save。我读过关于堆栈溢出的另一个答案建议仅使用updateChangeCount来触发UIManagedDocument保存。但是我没有读过Apple的任何内容,所以在这种情况下,调用UIManagedDocument saveToURL:forSaveOperation:completionHandler:方法可能会使所有内容保持同步并保存。

我想下一个明显的问题是如何通知NSFetchedResultsController发生了更改。我很想简化上面讨论的设置,然后订阅各种NSManagedObjectContextObjectsDidChangeNotification或在不同的上下文中保存通知,看看UIMangedDocument保存,自动保存或何时调用哪些(如果有的话)后台更改将保存到父级(假设在这种情况下允许)。我假设NSFetchedResultsController连接到这些通知,以便与基础数据保持同步。

或许您可能需要在主上下文中手动执行获取,合并或刷新以获取所需的更改,然后以某种方式通知NSFetchedResultsController它需要刷新?

就个人而言,我想知道UIManagedDocument是否已准备好进行一般消费,今年在WWDC上没有提及它,而是提出了如何构建更复杂的解决方案的冗长讨论:“会议227 - 将iCloud与核心数据结合使用“

答案 1 :(得分:1)

在我从服务器获取数据的方法中,我首先创建实体,之后我调用这两种方法来保存对文档的更改:

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


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

答案 2 :(得分:0)

由于您要在不同的上下文中更新结果,因此我认为您需要在视图控制器[self.fetchedResultsController performFetch:&error]方法中调用-viewWillAppear:


更新后

好的,您不应该致电[backgroundContext save:&error][document.managedObjectContext save:nil]。请参阅:UIManagedDocument Class Reference

  

您通常应使用标准的UIDocument方法来保存文档。   如果直接保存子上下文,则只提交对父上下文的更改,而不是对文档存储的更改。如果直接保存父上下文,则可以回避文档执行的其他重要操作。

我必须使用-insertedObjectsobtainPermanentIDsForObjects:error:来保留在上下文中创建的新对象。

接下来,我认为您不需要创建在后台运行的新上下文。 document.managedObjectContext.parentContext应该是可用的背景上下文来运行更新。

最后,我不经常致电[document updateChangeCount:UIDocumentChangeDone]。这由文档自动处理。您仍然可以随时进行,但不一定非必要。

以下是我称之为-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);
    }
}];