【问题标题】:Crashing issue in using UITableViewController and Core Data使用 UITableViewController 和 Core Data 时的崩溃问题
【发布时间】:2012-08-12 13:02:05
【问题描述】:

我在使用带有 Core Data 的 TableView 时遇到了崩溃问题,非常感谢任何帮助。场景是:

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

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

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

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

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

我的信念正确吗?如果是这样,我应该如何处理这个时间延迟?

非常感谢。

【问题讨论】:

    标签: ios core-data tableview


    【解决方案1】:

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

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

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

    这是我的实现:

    #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)从刷新的后台线程上下文中调用“save”,提交删除。 (2) 后台线程上下文触发 NSManagedObjectContextDidSaveNotification (3) 适当的选择器对主队列上的主线程上下文执行合并调用。时间延迟介于 (1) 和 (3) 之间,非常小 - 但如果正在滚动表格视图,则足以导致崩溃。我想即使我将刷新方式改为更新,它仍然会发生?
    【解决方案2】:

    我自己没有尝试过,但是看看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 线程,因此如果您在刷新期间替换所有核心数据对象您也可以在每个 WillSave 上禁用 frc 并在每个 DidSave 上重新获取。

    如果您负担得起以 iOS 5+ 为目标平台,那么我建议您探索嵌套上下文 - here is a nice overview of approaches

    【讨论】:

    • 您好 pwgr,我已经试验了您的想法。 NSManagedObjectContextWillSaveNotification 没有帮助,因为通知本身是异步发布的,所以当我的处理程序是同步的时,MOC 保存将继续进行,而无需等待我的处理程序完成。
    • 现在我所做的是借用您的 NSManagedObjectContextWillSaveNotification 想法,方法是在我的刷新模块上定义一个委托,以便它在刷新之前和之后调用我的委托方法。然后我将我的表视图控制器设置为委托,因此能够在刷新之前做一些事情。听取您的建议,我想在刷新之前禁用 frc,然后在刷新后再次启用它。我的问题是:如何“禁用”frc?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-05-02
    • 1970-01-01
    • 2016-10-27
    • 2010-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多