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

发布于 2024-09-12 17:20:45 字数 4298 浏览 5 评论 0原文

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

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

现在关于我的问题:

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

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

First of all, sorry for the too long question.

I know that there are few questions here that discuss similar issues but none of these talks about NSFetchedResultsController with delegate together with update in separate thread. And none of the solutions has helped me.
These are the existing questions:

Now about my problem:

  • I have a separate thread that updates the core data objects from the web (using a socket).
  • There are few view controllers that display the data from the same core data object (each tab contains a view controller that displays its filtered data).
  • Each view controller has its own instance of NSFetchedResultsController and the delegate is set to self.

Sometimes I receive was mutated while being enumerated exception on updating the data in the separate thread and sometimes it crashes the app.

I have done many code manipulations in order to try to fix it and seems that nothing helps.
I have tried not to use the managed object directly from table view datasource methods. Instead of that I have created an array which holds a list of dictionaries. i fill those dictionaries in the didChangeObject method from above. This way I don't touch the managed objects at all in the view controller.

And then I have understood that the problem is in NSFetchedResultsController that, probably, iterates the data all the time. And this is the object that conflicts with my data update in the separate thread.

The question is how can I update the core data objects in the separate thread once I have a NSFetchedResultsController with delegate (meaning that it "watches" the data and updates the delagate all the time).

NSFetchedResultsControllerDelegate implementation:

- (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];
}

In the table view datasource methods I work directly with the managed object.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

伊面 2024-09-19 17:20:45

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

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

就这么简单。

更新

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

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

其次,是的,每个线程应该有一个上下文,这是核心数据和多线程的黄金规则之一(另一个是不要跨线程传递 NSManagedObject 实例)。这可能是您崩溃的根源,如果不是,它将成为将来崩溃的根源。

更新

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

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

然而,在应用程序完成之前担心性能是应该避免的预优化。猜测什么会表现良好,什么不会表现通常是一个坏主意。

Two separate questions here. First, if you are getting mutating errors that means you are mutating a set or array (or relationship) while iterating over that set/array/relationship. Find where you are doing that and stop doing it. That is the only solution.

As for your updates. Your background NSManagedObjectContext should be saving periodically. Your main thread should be listening for NSManagedObjectContextDidSaveNotification and when it receives one it calls the main NSManagedObjectContext on the main thread (as the notification will most likely come in on the background thread) via -mergeChangesFromContextDidSaveNotification: which takes the NSNotification as a parameter. This will cause all of your NSFetchedResultController instances to fire their delegate methods.

Simple as that.

Update

ank you for your reply. The exception is thrown on updating the NSManagedObjectContext in the background thread. I use the same NSManagedObjectContext in both threads. The app should be as close as possible to real time app - the updates constantly and the tables should be updated immediately. I don't save at all - I only update the NSManagedObjectContext. I have seen in one of the questions mentioned that someone used to separate instances of NSManagedObjectContext but he still receive the same exceptions once he merges the changes. So, you suggest using 2 separate NSManagedObjectContext's?

First, read up on multi-threading in Core Data from Apple's documentation (or my book :).

Second, yes you should have one context per thread, that is one of the golden rules of Core Data and multi-threading (the other is don't pass NSManagedObject instances across threads). That is probably the source of your crash and if it isn't it is going to be the source of a crash in the future.

Update

I have tons of data and I update only the modified/new/deleted items in the table. If I will start saving then will it harm the performance?

No, only the updates will be propagated across the threads. The entire data store will not be re-read so it will actually improve performance when you break up the saves into smaller chunks because you will be on the main thread, updating the UI, in smaller chunks so the UI will appear to perform better.

However, worrying about performance before the app is completed is a pre-optimization that should be avoided. Guessing at what is going to perform well and what won't is generally a bad idea.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文