iPhone: 在单独的线程中更新数据并使用代理的NSFetchedResultsController

5

首先,很抱歉问题太长了。

我知道这里有一些讨论类似问题的问题,但没有一个讨论NSFetchedResultsController与委托一起使用以及在单独的线程中进行更新。而且没有任何解决方案能帮助我。
这些是现有的问题:

现在来谈谈我的问题:
  • 我有一个单独的线程,通过套接字从Web更新核心数据对象。
  • 有几个视图控制器显示来自相同核心数据对象的数据(每个选项卡包含一个显示其过滤数据的视图控制器)。
  • 每个视图控制器都有自己的NSFetchedResultsController实例,并将委托设置为self。

有时候在单独的线程中更新数据时会收到was mutated while being enumerated异常,有时会导致应用程序崩溃。

我已经进行了许多代码操作,以尝试修复它,似乎没有什么帮助。
我尝试不使用直接从表格视图数据源方法中管理对象。相反,我创建了一个包含字典列表的数组。我在上面的didChangeObject方法中填充这些字典。这样,在视图控制器中根本不触摸托管对象。

然后我明白了问题在于NSFetchedResultsController,它可能一直迭代数据。这就是与单独线程中的数据更新发生冲突的对象。

问题是,一旦我有了一个带有委托的NSFetchedResultsController(意味着它“监视”数据并始终更新委托),我如何在单独的线程中更新核心数据对象。

NSFetchedResultsControllerDelegate实现:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];
}
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    if ( self.tabBarController.selectedIndex == 0 ) {
        UITableView *tableView = self.tableView;
        @try {
            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:UITableViewRowAnimationNone];
                    break;
                case NSFetchedResultsChangeMove:
                    [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                    [tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade];
                    break;
            }
        }
        @catch (NSException * e) {
            NSLog(@"Exception in didChangeObject: %@", e);
        }
    }
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    if ( self.tabBarController.selectedIndex == 0 ) {
        @try {
            switch(type) {
                case NSFetchedResultsChangeInsert:
                    [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                    break;
                case NSFetchedResultsChangeDelete:
                    [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                    break;
            }           
        }
        @catch (NSException * e) {
            NSLog(@"Exception in didChangeSection: %@", e);
        }
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{
    [self.tableView endUpdates];
}

在表视图的数据源方法中,我直接使用托管对象。
1个回答

12

这里有两个不同的问题。首先,如果您收到变异错误,这意味着在迭代该集合/数组/关系时,您正在对集合或数组(或关系)进行变异。找到您正在执行此操作的位置并停止执行即可解决问题。

至于您的更新。您的后台NSManagedObjectContext应定期保存。您的主线程应侦听NSManagedObjectContextDidSaveNotification,并在接收到通知时通过-mergeChangesFromContextDidSaveNotification:调用主NSManagedObjectContext在主线程上(因为通知很可能会在后台线程上收到),其中将NSNotification作为参数。这将导致所有NSFetchedResultController实例触发其委托方法。

就这么简单。

更新

感谢您的回复。异常是在后台线程更新NSManagedObjectContext时抛出的。我在两个线程中都使用相同的NSManagedObjectContext。应用程序应尽可能接近实时应用程序-更新不断进行,表应立即更新。我根本没有保存-我只更新了NSManagedObjectContext。我曾经看过在提到这一点时有人使用了两个单独的NSManagedObjectContext实例,但是一旦他合并更改,他仍然收到相同的异常。所以,您建议使用2个单独的NSManagedObjectContext吗?

首先,请阅读Apple文档(或我的书)中有关Core Data和多线程的内容。

其次,是的,您应该为每个线程提供一个上下文,这是Core Data和多线程的黄金规则之一(另一个规则是不要跨线程传递NSManagedObject实例)。这可能是崩溃的来源,如果不是现在,将来也会出现崩溃。

更新

我有大量数据,只更新表中修改/新建/删除的项目。如果我开始保存,它会影响性能吗?

不会的,只有更新会在线程之间传播。整个数据存储区将不会重新读取,因此当你将保存操作分成较小的块时,它实际上会提高性能,因为你将在主线程上以较小的块来更新 UI,所以 UI 看起来会表现得更好。

然而,在应用程序完成之前担心性能是一种应该避免的预优化。猜测哪些代码会表现良好,哪些又不行通常都是一个糟糕的想法。


谢谢您的回复。在后台线程更新NSManagedObjectContext时抛出异常。我在两个线程中使用相同的NSManagedObjectContext。应用程序应尽可能接近实时应用程序 - 更新不断进行,表格应立即更新。我根本不保存 - 我只更新NSManagedObjectContext。我曾经看到有人在一个问题中提到过使用分离的NSManagedObjectContext实例,但是一旦合并更改,他仍然会收到相同的异常。所以,您建议使用2个单独的NSManagedObjectContext吗? - Michael Kessler
我有大量的数据,只更新表中已修改/新增/删除的项目。如果开始保存,会影响性能吗? - Michael Kessler
谢谢Marcus。我下周会尝试你的建议。顺便说一句,当你编辑答案时,我在个人资料中看不到它 - 我通常在编辑答案时也会添加评论... - Michael Kessler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接