8

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?

Kedar Paranjape
  • 1,822
  • 2
  • 22
  • 33
  • You don't show the code where you update the table after the network operation completes but I would guess that you aren't dispatching the update on the main queue. – Paulw11 Mar 10 '15 at 07:26
  • Well, if by "dispatching" the update, you mean calling `save`, then no, I'm calling `save` from the `NSURLSession` delegate method `URLSession:downloadTask:didFinishDownloadingToURL:` which is NOT on the main thread if I'm not mistaken? – Kedar Paranjape Mar 10 '15 at 07:34
  • So you're using a background thread context you created and saving the change up? – Wain Mar 10 '15 at 07:46
  • No, I'm not using any parent/child contexts. There's simply one context object inside my AppDelegate declared as `atomic` which the `NSURLSession` threads are using. – Kedar Paranjape Mar 10 '15 at 07:49
  • 1
    No, I mean are you wrapping any actions that update the UI (such as reload on the tableview) in a `dispatch_async(dispatch_get_main_queue,^{ UIUpdate code here });` – Paulw11 Mar 10 '15 at 07:58
  • @Paulw11 NSFRC works when updates are NOT on the main queue. The update happens when the main context is merged with a child context. Iirc – Fogmeister Mar 10 '15 at 08:17
  • @Paulw11 I don't update the tableview directly from any non-main thread. All tableview updating is done via the `NSFetchedResultsController` delegates that I've implemented. Not having to update the tableview from other threads was one main reason for using the NSFRC in the first place – Kedar Paranjape Mar 10 '15 at 08:24
  • Can you clarify when the delay occurs: after saving context, before controllerWillChangeContent, or after controllerDidChangeObjectAtIndexPath, before configureCell, etc – pbasdf Mar 10 '15 at 08:48
  • @Paulw11 I just called context `save` on the main queue (via `dispatch_get_main_queue..`, and updates are now propagated instantly! The `NSFRC`'s delegate methods always run on the main thread. However the delay seems to be occuring because I'm performing a `save` in a background thread? I still find the behavior strange though - my background `save` actually did save data & inform the `NSFRC` too. It just introduces a tableview refresh delay somehow. Any thoughts? – Kedar Paranjape Mar 10 '15 at 08:53
  • @pbasdf See above comment. `save` occurs (on a background thread), and all 3 methods of the `NSFRC` are called on time. Even `configureCell` does its work(of setting cell contents). The tableview simply sits there for some time before updating. – Kedar Paranjape Mar 10 '15 at 08:56
  • A very common cause of UI delays is code executing on a background thread. My guess is that the save to the NSFRC on a background thread results in the delegate methods being called on the background thread - if you don't then explicitly dispatch the code in the delegate methods on the main queue it will be performed on the background queue too. – Paulw11 Mar 10 '15 at 09:04
  • @Paulw11 Oh! Delegate methods being called on background threads! Whew! However `[NSThread currentThread]` says that the delegate methods are being called on the main thread! Is there something in between `save` and the delegates being called thats causing the delay? – Kedar Paranjape Mar 10 '15 at 09:09

1 Answers1

4

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

Kedar Paranjape
  • 1,822
  • 2
  • 22
  • 33