简体   繁体   English

使用UITableViewController和Core Data时发生崩溃的问题

[英]Crashing issue in using UITableViewController and Core Data

I am having a crash problem when using a TableView with Core Data and any help is greatly appreciated. 将TableView与Core Data一起使用时遇到崩溃问题,我们将不胜感激。 The scenario is: 该方案是:

  • I have a UITableViewController showing data stored in Core Data. 我有一个UITableViewController,它显示存储在Core Data中的数据。 I use a NSFetchResultsController to do the fetching as instructed by the documentation. 我使用NSFetchResultsController按照文档的说明进行提取。 I have a dedicated NSManagedObjectContext exclusive for the main thread to fetch the data. 我有专用于主线程的专用NSManagedObjectContext,以获取数据。

  • The data is actually coming from a server. 数据实际上来自服务器。 When my application launches, I have a background thread to refresh the data into my Core Data stack. 当我的应用程序启动时,我有一个后台线程将数据刷新到我的Core Data堆栈中。 As recommended by Apple, I use a different NSManagedObjectContext in the background thread to refresh the data. 按照Apple的建议,我在后台线程中使用了另一个NSManagedObjectContext来刷新数据。 During the refresh, old data will be deleted. 刷新期间,旧数据将被删除。

  • After the background saves the changes, I used the NSManagedObjectContextDidSaveNotification to trigger a call to do the mergeChangesFromContextDidSaveNotification on the main thread's context. 后台保存更改后,我使用NSManagedObjectContextDidSaveNotification触发了对主线程上下文执行mergeChangesFromContextDidSaveNotification的调用。

  • controllerDidChangeContent of the FRC is also implemented which call the UITableViewController to reload. 还实现了FRC的controllerDidChangeContent,它调用UITableViewController重新加载。

Everything works fine - except if I scroll the TableView while the data refresh is in progress, the app will crash with the "Core Data could not fulfill a fault..." error. 一切正常-除非在数据刷新过程中滚动TableView,否则该应用程序将崩溃并显示“核心数据无法完成错误...”错误。 After tracing through the code, I believe the cause is there is a small time lag between the background thread saving the delete of the data and the main thread context merge operation. 在遍历代码之后,我相信原因是在后台线程保存数据删除和主线程上下文合并操作之间存在较小的时间间隔。 During this time lag, some of the managed objects in the main thread's context is deleted and so when the table is scrolled and the data source method access the deleted object, the app will crash. 在此时间间隔内,主线程上下文中的某些托管对象将被删除,因此,在滚动表并且数据源方法访问已删除的对象时,应用程序将崩溃。

Is my belief correct? 我的信念正确吗? If so, how should I deal with this time lag? 如果是这样,我应该如何处理这个时滞?

Many Thanks. 非常感谢。

Umm, there is no lag once the merge is done. 嗯,合并完成后就没有滞后了。 The data will me available. 数据将可用。 Your problem seems to be of a different nature. 您的问题似乎具有不同的性质。 The first thing i can tell you is that your approach is somewhat incorrect. 我可以告诉您的第一件事是您的方法有些不正确。 you should not be deleting data just to do a refresh. 您不应该只是为了刷新而删除数据。 a proper update is the efficient way to go. 适当的更新是行之有效的方法。

That being said, here's a couple things to consider: 话虽这么说,这里有几件事情要考虑:

  1. Make sure the merge call with the main thread's managed object context is being done in the main thread. 确保在主线程中完成了与主线程的托管对象上下文的合并调用。 If you are not doing so, your main thread's context will be triggering the notifications for the NSFetchedResultsController in the thread where it was calling from, and it will call your delegate methods for the NSFetchedResultsController in that thread, potentially updating your UI outside the main thread. 如果您不这样做,则主线程的上下文将在其调用所在的线程中触发NSFetchedResultsController的通知,并且它将在该线程中调用NSFetchedResultsController的委托方法,从而有可能在主线程之外更新您的UI 。
  2. Make sure you do the correct procedure on the NSFetchedResults delegate. 确保对NSFetchedResults委托执行正确的过程。

Here's my implementation: 这是我的实现:

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

Haven't tried this myself, but take a look at NSManagedObjectContextWillSaveNotification . 我自己还没有尝试过,但是请看一下NSManagedObjectContextWillSaveNotification I would try registering for those notifications coming from background context. 我会尝试注册那些来自后台上下文的通知。 Then in the handler you could do a synchronous dispatch to main thread and pass contexts' deleted object IDs: 然后,您可以在处理程序中对主线程进行同步调度,并传递上下文的已删除对象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
    });
}

Since the dispatch is synchronous, it should block actual deletion until you deal with it in main thread's context. 由于分派是同步的,因此它应该阻止实际的删除,直到您在主线程的上下文中处理它为止。 Keep in mind this wasn't tested and may result in some nasty deadlocks. 请记住,这未经测试,可能会导致一些令人讨厌的僵局。

Another thing worth pointing out is that interactive updates (as in NSFetchedResultsControllerDelegate implementation) will hog UI thread when there's a lot of objects changing (like hundreds/thousands), so if you replace all of your Core Data objects during refresh you may as well disable frc on every WillSave and refetch on every DidSave. 值得一提的另一件事是,当有许多对象发生更改(例如成百上千个)时,交互式更新(如NSFetchedResultsControllerDelegate实现)将占用UI线程,因此,如果在刷新期间替换所有Core Data对象,则也可以禁用在每个WillSave上显示frc,在每个DidSave上重新提取。

If you can afford targeting iOS 5+ then I would suggest exploring nested contexts - here is a nice overview of approaches . 如果您可以负担得起针对iOS 5+的价格,那么我建议您探索嵌套的上下文- 这里是方法的很好概述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM