[英]Crashing issue in using UITableViewController and Core Data
将TableView与Core Data一起使用时遇到崩溃问题,我们将不胜感激。 该方案是:
我有一个UITableViewController,它显示存储在Core Data中的数据。 我使用NSFetchResultsController按照文档的说明进行提取。 我有专用于主线程的专用NSManagedObjectContext,以获取数据。
数据实际上来自服务器。 当我的应用程序启动时,我有一个后台线程将数据刷新到我的Core Data堆栈中。 按照Apple的建议,我在后台线程中使用了另一个NSManagedObjectContext来刷新数据。 刷新期间,旧数据将被删除。
后台保存更改后,我使用NSManagedObjectContextDidSaveNotification触发了对主线程上下文执行mergeChangesFromContextDidSaveNotification的调用。
还实现了FRC的controllerDidChangeContent,它调用UITableViewController重新加载。
一切正常-除非在数据刷新过程中滚动TableView,否则该应用程序将崩溃并显示“核心数据无法完成错误...”错误。 在遍历代码之后,我相信原因是在后台线程保存数据删除和主线程上下文合并操作之间存在较小的时间间隔。 在此时间间隔内,主线程上下文中的某些托管对象将被删除,因此,在滚动表并且数据源方法访问已删除的对象时,应用程序将崩溃。
我的信念正确吗? 如果是这样,我应该如何处理这个时滞?
非常感谢。
嗯,合并完成后就没有滞后了。 数据将可用。 您的问题似乎具有不同的性质。 我可以告诉您的第一件事是您的方法有些不正确。 您不应该只是为了刷新而删除数据。 适当的更新是行之有效的方法。
话虽这么说,这里有几件事情要考虑:
这是我的实现:
#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];
}
我自己还没有尝试过,但是请看一下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+的价格,那么我建议您探索嵌套的上下文- 这里是方法的很好概述 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.