【问题标题】:UIManagedDocument with NSFetchedResultsController and background context带有 NSFetchedResultsController 和背景上下文的 UIManagedDocument
【发布时间】:2012-07-06 05:33:45
【问题描述】:

我正在尝试使以下工作。

我有一个表格视图,它在表格视图中显示从 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 上),那么它可以正常工作...

【问题讨论】:

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


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

      假设它不是,听起来你想要做的事情在嵌套上下文中应该是完全可能的,因此假设UIManagedDocument 没有错误。

      我唯一的保留是我一直在尝试使用 UIManagedDocument 进行批量加载,但它似乎不适用于嵌套上下文 (https://stackoverflow.com/q/11274412/1347502)。我认为NSFetchedResultsController 的主要好处之一是它能够通过批量加载来提高性能。所以如果这不能在UIManagedDocument 中完成,也许NSFetchedResultsController 还没有准备好与UIManagedDocument 一起使用,但我还没有解决这个问题。

      除此之外,我阅读或查看的有关嵌套上下文和后台工作的大部分说明似乎都是使用 peer 子上下文完成的。您所描述的是父、子、孙配置。在 WWDC 2012 视频“Session 214 - Core Data Best Practices”(+ 16:00 分钟)中,Apple 建议在此场景的父上下文中添加另一个对等上下文,例如

      backgroundContext.parentContext = document.managedObjectContext.parentContext;
      

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

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

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

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

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

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

      就我个人而言,我想知道UIManagedDocument 是否已准备好供大众使用,今年的 WWDC 上没有提及它,而是提出了关于如何构建更复杂的解决方案的冗长讨论:“第 227 场会议 - 使用带有核心数据的 iCloud"

      【讨论】:

        【解决方案3】:

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


        更新后

        好的,你不应该打电话给[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);
            }
        }];
        

        【讨论】:

        • 嗯,我以为不同上下文的变化会被合并,所以最终会通知fetchedResultsController。无论如何,我尝试在 backgroundContext 保存后调用 [self.fetchedResultsController performFetch:nil],但情况仍然相同:我的表视图显示两个实体,而应该只有一个。重启应用后,一切正常。
        • 还记得我有一个 UIDocument,也许这会改变一些事情?我基本上想在后台线程中将新实体保存/更新到我的文档中。如果我在 UIDocument 的 managedObjectContext 上执行保存,则 UI 会滞后。这就是为什么我尝试使用 backgroundContext 来做这件事,但不知何故,事情变得一团糟/没有合并/没有线索发生了什么。我现在很绝望。
        • 不幸的是,您的代码仍然不适合我。它给了我完全奇怪和不可预测的结果。对我有用的是使用背景上下文,然后调用保存并获取该背景上下文的永久 ID。但是,如果该更新代码同时被调用两次,我现在遇到了竞争条件的问题,我认为这是因为它在两个不同的上下文中创建实体,然后合并它们。不知道如何防止这种情况。
        • @user1013725 对不起,但我认为你在这里跑到了一条死胡同。使用托管文档时,Apple 不支持上下文 -save:。也许在获得永久 ID 后,您应该致电 [document updateChangeCount:UIDocumentChangeDone]
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-04
        相关资源
        最近更新 更多