2

我有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 作为第一个条目。既不是也不是nil其他有趣的东西。

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

编辑 2:我将尝试解释 MOC 之间的关系。

有三个托管对象上下文在起作用:

1)UIManagedDocument.managedObjectContext通过加载文档创建。

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

context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
context.parentContext = repository.managedObjectContext;

[context performBlock:^{ /* updates */ }];
[context performBlock:^{ [context save:NULL]; }];

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

后台更新是从 NSOperationQueue 完成的,但是对后台 MOC 的所有访问都正确地用-performBlock:. 所有其他访问都是从主线程完成的。

编辑3:在玩一些设置时,NSFetchRequest我发现设置时问题消失了fetchRequest.includesPendingChanges = NO。但这不是一个可行的解决方案,因为现在用户看不到任何更新,直到 UIManagedDocument 将更改保存在后台。

4

1 回答 1

4

我这里有几个危险信号。首先,这个...

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

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

[document updateChangeCount:UIDocumentChangeDone];

如果您使用 UndoManager,这将自动完成。因此,如果您直接输入 document.managedContext,您只需调用上述方法通知托管文档更改已完成,其他一切都应自动处理。

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

但是一旦我在子上下文中显示列表,

子上下文是什么?为什么任意上下文应该看到任何东西?它是 UIManagedDocument 的主要上下文的子上下文,还是父上下文,还是其他什么?

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

当我终于完全了解正在发生的事情时(具有讽刺意味的是,通过阅读有关 iCloud 的工作原理),我所有与 UIManagedDocument 相关的问题似乎都消失了。

基本上,遵循这些简单的规则:

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

  2. 当对象“脏”并且需要保存时,要么向 undo-manager 注册更改,要么直接调用 [doc updateChangeCount:UIDocumentChangeDone]。

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

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

如果您使用多个上下文连续插入/获取,您将有一些问题需要解决。主要是,您必须确保您的更改被传播并计划保存,然后您的获取执行您希望他们执行的操作。

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

提取时,您必须决定是否希望您的提取一直沿上下文链向上。NSFetchRequest 上有很多选项(例如 setShouldRefreshRefetchedObjects)。这些选项将决定 fetch 是仅使用其自己的上下文,还是转到后备存储以及许多其他内容。大多数情况下,默认值是您想要的,但是在使用子上下文时,您有更多的责任。

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

编辑

针对您最近的问题编辑中的这一声明:

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

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

于 2012-04-17T19:38:42.777 回答