使用带有Core Data的TableView时出现崩溃问题,非常感谢任何帮助。场景是:
我有一个UITableViewController,显示存储在Core Data中的数据。我使用NSFetchResultsController按照文档的指示进行提取。我有一个专用的NSManagedObjectContext专用于主线程来获取数据。
数据实际上来自服务器。当我的应用程序启动时,我有一个后台线程来将数据刷新到我的Core Data堆栈中。根据Apple的建议,我在后台线程中使用不同的NSManagedObjectContext来刷新数据。在刷新期间,旧数据将被删除。
在后台保存更改后,我使用NSManagedObjectContextDidSaveNotification触发调用,在主线程的上下文中执行mergeChangesFromContextDidSaveNotification。
还实现了FRC的controllerDidChangeContent,调用UITableViewController重新加载。
一切正常 - 除非我在数据刷新过程中滚动TableView,否则应用程序将因“核心数据无法完成故障...”错误而崩溃。在遍历代码之后,我相信原因是后台线程保存数据删除与主线程上下文合并操作之间存在小的时间延迟。在此时间延迟期间,主线程上下文中的某些托管对象将被删除,因此当滚动表并且数据源方法访问已删除的对象时,应用程序将崩溃。
我的信念是否正确?如果是这样,我应该如何处理这个时间滞后?
非常感谢。
答案 0 :(得分:0)
话虽如此,这里有几件事需要考虑:
这是我的实施:
#pragma mark -
#pragma mark NSFetchedResultsControllerDelegate methods
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller{
[self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
switch(type) {
case NSFetchedResultsChangeInsert:
[self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
[self.tableView endUpdates];
}
答案 1 :(得分:0)
我自己没试过,但请看NSManagedObjectContextWillSaveNotification
。
我会尝试注册来自后台上下文的通知。然后在处理程序中,您可以对主线程执行同步调度并传递上下文的已删除对象ID:
- (void)handleBackgroundSave:(NSNotification *)note {
NSManagedObjectContext *context = [note object];
NSSet *deletedObjectIDs = [[context deletedObjects] valueForKey:@"objectID"];
dispatch_sync(dispatch_get_main_queue(), ^{
// deletedObjectIDs can be passed across threads
// if NSFetchedResultsController's fetchedObjects contains deleted objects
// you have to disable it and refetch after DidSaveNotification
});
}
由于调度是同步的,因此它应该阻止实际删除,直到您在主线程的上下文中处理它为止。请记住,这未经过测试,可能会导致一些令人讨厌的死锁。
另一件值得指出的事情是,当有很多对象发生变化时(例如数百/数千),交互式更新(如在NSFetchedResultsControllerDelegate
实现中)将占用UI线程,因此如果替换所有的Core Data对象在刷新期间,你也可以在每个WillSave上禁用frc并在每个DidSave上重新获取。
如果你能负担得起iOS 5+的定位,那么我建议探索嵌套的上下文 - here is a nice overview of approaches。