使用UITableViewController和Core Data时出现崩溃的问题

时间:2012-08-16 07:47:01

标签: ios core-data tableview

使用带有Core Data的TableView时出现崩溃问题,非常感谢任何帮助。场景是:

  • 我有一个UITableViewController,显示存储在Core Data中的数据。我使用NSFetchResultsController按照文档的指示进行提取。我有一个专用的NSManagedObjectContext专用于主线程来获取数据。

  • 数据实际上来自服务器。当我的应用程序启动时,我有一个后台线程来将数据刷新到我的Core Data堆栈中。根据Apple的建议,我在后台线程中使用不同的NSManagedObjectContext来刷新数据。在刷新期间,旧数据将被删除。

  • 在后台保存更改后,我使用NSManagedObjectContextDidSaveNotification触发调用,在主线程的上下文中执行mergeChangesFromContextDidSaveNotification。

  • 还实现了FRC的controllerDidChangeContent,调用UITableViewController重新加载。

一切正常 - 除非我在数据刷新过程中滚动TableView,否则应用程序将因“核心数据无法完成故障...”错误而崩溃。在遍历代码之后,我相信原因是后台线程保存数据删除与主线程上下文合并操作之间存在小的时间延迟。在此时间延迟期间,主线程上下文中的某些托管对象将被删除,因此当滚动表并且数据源方法访问已删除的对象时,应用程序将崩溃。

我的信念是否正确?如果是这样,我应该如何处理这个时间滞后?

非常感谢。

2 个答案:

答案 0 :(得分:0)

嗯,合并完成后没有延迟。我将提供这些数据。你的问题似乎有所不同。我可以告诉你的第一件事是你的方法有些不正确。你不应该只是为了刷新而删除数据。适当的更新是有效的方法。

话虽如此,这里有几件事需要考虑:

  1. 确保在主线程中完成与主线程的托管对象上下文的合并调用。如果你没有这样做,你的主线程的上下文将在它调用的线程中触发NSFetchedResultsController的通知,并且它将在该线程中调用NSFetchedResultsController的委托方法,可能会在主线程之外更新你的UI
  2. 确保在NSFetchedResults委托上执行正确的过程。
  3. 这是我的实施:

    #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