简体   繁体   中英

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. The scenario is:

  • I have a UITableViewController showing data stored in Core Data. I use a NSFetchResultsController to do the fetching as instructed by the documentation. I have a dedicated NSManagedObjectContext exclusive for the main thread to fetch the data.

  • 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. As recommended by Apple, I use a different NSManagedObjectContext in the background thread to refresh the data. 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.

  • controllerDidChangeContent of the FRC is also implemented which call the UITableViewController to reload.

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. 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.
  2. Make sure you do the correct procedure on the NSFetchedResults delegate.

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 . 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:

- (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.

If you can afford targeting iOS 5+ then I would suggest exploring nested contexts - here is a nice overview of approaches .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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