Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSFetchedResultsController consistent delay in updating UITableView cell; insert works instantaneously

I have a UITableView (with custom cells, if that matters) hooked up to an NSFetchedResultsController. My app also uses push notifications. When a remote notification arrives, I update my (core)data model in two phases:

1) Get title (with some custom data) and store it as a new core data Entity. Here I call NSManagedObjectContext save(). NSFetchedResultsController picks up the insert and fires it's delegate method didChangeObject with NSFetchedResultsChangeType=NSFetchedResultsChangeInsert. The tableview is instantly updated. (A new row is inserted)

2) Download more content related to the cell via NSURLSession insert it into the above Entity and again call the save() method. NSFetchedResultsController again picks up the update and fires it's delegate method didChangeObject with NSFetchedResultsChangeType=NSFetchedResultsChangeUpdate. This is where I call my configureCell method. The tableView is updated but with a consistent delay of about 10 seconds.

At both stages (download-context save-update tableview) the data that needs to be shown has already been persisted by core data. (For e.g. inside my configureCell.. method, I know I'm not setting any cell label to nil or suchlike.

My NSFetchedResultsController delegate methods:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
[self.mainTableView beginUpdates];
}

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


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.mainTableView;

switch(type) {

    case NSFetchedResultsChangeInsert:
        [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
        break;

    case NSFetchedResultsChangeUpdate:
        [self configureCell:(MessageTableViewCell *)[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
    }
}

Other things that I've tried inside case NSFetchedResultsChangeUpdate:

  1. Tried calling processPendingChanges before context save
  2. Called reloadRowsAtIndexPaths instead of calling configureCell directly
  3. Tried wrapping the update.. methods inside beginUpdates and endUpdates(even though I already have one pair which should do the job)
  4. [tableView reloadData]
  5. numberOfRowsInSection is not 0!
  6. The NSFetchedResultsController delegate methods run on the main thread only.

In a nutshell, all (?) the proper delegate methods are being called correctly. Yet I see a consistent ~10 sec delay when updating a cell. However, insertion happens almost instantaneously. Any ideas?

like image 472
Kedar Paranjape Avatar asked Mar 10 '15 07:03

Kedar Paranjape


1 Answers

@Paulw11 's comment about dispatch_async... set me off in the right direction. The problem lies in the fact that I have been using the same ManagedObjectContext object in two different threads even though they do not simultaneously access it at the same time. As soon as I dispatched my save calls (which were on a background thread) on the main queue (by wrapping it inside dispatch_async(dispatch_get_main_queue,^{ UIUpdate code here });), the delays disappeared. A possible explanation would be that calling save on a background thread results in the delegate methods being called on the background thread.

Anyway, back to the solution - never share the same ManagedObjectContext object between multiple threads. Use parent/child context relationships if you're updating core date in more than one thread. NSFetchedResultsController also plays nicely with this pattern.

like image 60
Kedar Paranjape Avatar answered Oct 22 '22 18:10

Kedar Paranjape