NSFetchedResultsController无法在子上下文中获取

时间:2012-04-17 09:09:48

标签: ios core-data nsfetchedresultscontroller uimanageddocument

我有UIManagedDocument一些数据,我使用NSFetchedResultsController在列表中显示。数据会在后台定期更新,更改会放在UIManagedDocument.managedObjectContext上(使用performBlock :)。

当我显示文档主要上下文中的数据时,所有数据都按预期工作。但是,只要我在主要上下文(child.parentContext = document.managedObjectContext)的子上下文中显示列表,我就看不到任何对象,并且控制台上会打印以下错误:

foo[17895:15203] CoreData: error: (NSFetchedResultsController)
                 The fetched object at index 5 has an out of order section name 'E.
                 Objects must be sorted by section name'

仅在将新对象插入文档联系人后才会发生这种情况。当我等待足够长时间进行自动保存时,列表显示正常。此问题仅在我sectionNameKeyPath上设置了NSFetchedResultsController并且只有子上下文时才会出现。

这就是我设置获取结果控制器的方式,没有什么花哨的,所以我看不出我在这里做错了什么:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:@"Contact"];
fetchRequest.sortDescriptors = [Contact userDefinedSortDescriptors];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"hidden == nil || hidden == NO"];

NSFetchedResultsController *fetchedResultsController = 
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                    managedObjectContext:_managedObjectContext
                                      sectionNameKeyPath:[Contact userDefinedSectionNameKeyPath]
                                               cacheName:@"ContactList"];

[Contact userDefinedSortDescriptors][Contact userDefinedSectionNameKeyPath]在运行时解析。排序描述符包含sectionNameKeyPath作为第一个条目。 <{1}}或其他有趣的东西都不可能。

修改:澄清了一些模糊的部分。具体来说,我不会在文档托管对象上下文中调用-save:。

编辑2 :我将尝试解释MOC如何相互关联。

有三个托管对象上下文:

1)通过加载文档创建的nil

2)运行后台线程,不时更新对象。这是一个私有队列moc,文档MOC作为父文件:

UIManagedDocument.managedObjectContext

3)当用户想要进行更改时,将创建新的MOC作为文档MOC的子级。这是一个主要的队列MOC。这是用于执行上面显示的提取的上下文。

后台更新是从NSOperationQueue完成的,但所有对后台MOC的访问都被context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; context.parentContext = repository.managedObjectContext; [context performBlock:^{ /* updates */ }]; [context performBlock:^{ [context save:NULL]; }]; 正确包围。所有其他访问都是从主线程完成的。

编辑3 :在使用-performBlock:上的某些设置时,我发现设置NSFetchRequest时问题就消失了。但这不是一个可行的解决方案,因为现在用户不再看到任何更新,直到UIManagedDocument在后台保存更改。

1 个答案:

答案 0 :(得分:4)

这里有几个红旗。首先,这一个...

数据在后台定期更新,更改保存在UIManagedDocument.managedObjectContext上(使用performBlock :)。

UIManagedDocuments实现自动保存,我们唯一应该直接调用save方法是在初始创建时。所有其他“保存”应通过自动保存界面完成。基本上,这将是调用

[document updateChangeCount:UIDocumentChangeDone];

如果您使用UndoManager,则会自动完成此操作。因此,如果您直接提供给document.managedContext,您所要做的就是调用上面的方法来通知托管文档已完成更改,其他所有内容都应自动处理。

其次,我不确定你的意思是什么:

但是,只要我在子上下文中显示列表,

孩子的背景是什么?为什么任意上下文会看到什么?它是UIManagedDocument主要上下文的子上下文,还是父项或其他内容?

文档很清楚(对于某些明确的定义),但不幸的是,它需要阅读UIDocument,UIManagedDocument和所有NSManagedOjectContext内容的完整文档集。

当我最终围绕正在发生的事情(讽刺的是,阅读iCloud如何工作)时,我与UIManagedDocument相关的所有问题似乎都消失了。

基本上,请遵循以下简单规则:

  1. 不要直接在UIManagedDocument中嵌入的任何上下文中调用任何“save”方法。

  2. 当对象“脏”且需要保存时,可以使用撤消管理器注册更改,也可以直接调用[doc updateChangeCount:UIDocumentChangeDone]。

  3. 如果您想使用子语境,请继续。在这种情况下,您需要做的就是设置parentContext属性,并在您的子上下文中调用save :.其他一切都会自动发生。

  4. 修改 另一个红旗是您使用不同的上下文运行代码。如果是这种情况,您必须确保从正确的线程运行。 NSManagedObjectContext对象不是线程安全的。因此,必须始终从创建它们的线程(如果是NSConfinementConcurrencyType)访问它们,或者使用performBlock(如果是其他两种并发类型之一)。

    如果您使用多个上下文不断插入/获取,则需要解决一些问题。主要是,您必须确保您的更改已传播并计划保存,然后您的提取按照您希望的方式执行。

    完成插入后,请确保仅使用UIManagedDocument的子上下文调用save。切勿在托管文档上调用save或saveToURL。使用上述方法保存到它。这将确保您正确传播更改。

    在获取时,您必须决定是否希望您的提取在上下文链中一直向前移动。 NSFetchRequest上有很多选项(如setShouldRefreshRefetchedObjects)。这些选项将决定fetch是使用它自己的上下文,还是去后备存储,还有很多其他东西。默认值是您大多数时候想要的,但在使用子上下文时,您有更多的责任。

    此外,如果您有子上下文,您必须确保使用正确的并发类型创建它们,并且您只在适当的线程/队列中使用它们。否则你将得到未定义的结果。

    修改

    回应您最近的问题编辑中的这句话:

    3)当用户想要进行更改时,将新MOC创建为文档MOC的子级。这是一个主要的队列MOC。这是用于执行上面显示的提取的上下文。

    我从来没有这样做,对我来说,是另一个红旗。已有一个主队列MOC - document.managedObjectContext。如果你想在主线程中弄脏文档,请使用它。请记住,CoreData仍然在线程包含模型上运行,并且每个线程实际上只需要一个MOC。新的API提供了让MOC与队列相关联的方法,并且有所帮助,但我敢打赌,每个线程有多个MOC可能会导致问题。一般来说,我会避免这种情况。