iPhone:NSFetchedResultsController,具有来自单独线程的委托和数据更新

Posted

技术标签:

【中文标题】iPhone:NSFetchedResultsController,具有来自单独线程的委托和数据更新【英文标题】:iPhone: NSFetchedResultsController with delegate and data update from a separate thread 【发布时间】:2010-07-29 16:23:05 【问题描述】:

首先,抱歉问题太长了。

我知道这里很少有讨论类似问题的问题,但这些都没有讨论 NSFetchedResultsController 与委托以及单独线程中的更新。没有一个解决方案对我有帮助。 这些是现有的问题:

NSFetchedResultsController: using of NSManagedObjectContext during update brings to crash Determining which core Data attribute/property change triggered a NSFetchedResultsController update Core Data executeFetchRequest throws NSGenericException (Collection was mutated while being enumerated) Collection was mutated while being enumerated. How to determine which set? 等

现在谈谈我的问题:

我有一个单独的线程来更新来自网络的核心数据对象(使用套接字)。 很少有视图控制器显示来自同一核心数据对象的数据(每个选项卡都包含一个显示其过滤数据的视图控制器)。 每个视图控制器都有自己的 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】:

这里有两个单独的问题。首先,如果您遇到变异错误,这意味着您在迭代该集合/数组/关系时正在改变一个集合或数组(或关系)。找到你正在做的事情并停止这样做。这是唯一的解决方案。

至于您的更新。您的背景NSManagedObjectContext 应该定期保存。您的主线程应该正在监听NSManagedObjectContextDidSaveNotification,当它收到一个时,它会通过@987654324 调用主线程NSManagedObjectContext 在主线程(因为通知很可能会进入后台线程) @ 将NSNotification 作为参数。这将导致您的所有 NSFetchedResultController 实例触发它们的委托方法。

就这么简单。

更新

感谢您的回复。在后台线程中更新 NSManagedObjectContext 时会引发异常。我在两个线程中使用相同的 NSManagedObjectContext。该应用程序应尽可能接近实时应用程序 - 不断更新,表格应立即更新。我根本不保存 - 我只更新 NSManagedObjectContext。我在提到的一个问题中看到有人曾经分隔 NSManagedObjectContext 的实例,但是一旦合并更改,他仍然会收到相同的异常。那么,您建议使用 2 个单独的 NSManagedObjectContext 吗?

首先,从 Apple 的文档(或我的书 :) 中阅读 Core Data 中的多线程。

第二,是的,每个线程应该有一个上下文,这是 Core Data 和多线程的黄金法则之一(另一个是不要跨线程传递 NSManagedObject 实例)。这可能是您崩溃的根源,如果不是,它将成为未来崩溃的根源。

更新

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

不,只有更新会跨线程传播。整个数据存储将不会被重新读取,因此当您将保存分成更小的块时,它实际上会提高性能,因为您将在主线程上,以更小的块更新 UI,因此UI 似乎会表现得更好。

但是,在应用完成之前担心性能是应该避免的预优化。猜测哪些会表现良好,哪些表现不佳通常是个坏主意。

【讨论】:

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

以上是关于iPhone:NSFetchedResultsController,具有来自单独线程的委托和数据更新的主要内容,如果未能解决你的问题,请参考以下文章

核心数据:NSFetchedResultsController 错误:尝试创建两个动画

NSFetchedResultsController 仅按第一个排序描述符排序

NSFetchedResultsController - TableView 中的 UI 更新不完整

如何在 tableView 行选择时更新我的​​ fetchedResultsController?

更改 NSFetchedResultsController 的 Fetch Request 和重新加载表数据的方法

如何在不通知 NSFetchedResultsController 的情况下更新 NSManagedObject 或 NSManagedObjectContext