0

I have several table views that send JSON requests to a server, store the results in core data, and display them using an NSFetchedResultsController. I was experimenting with GCD as follows:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    // Perform JSON requests.
    dispatch_async(dispatch_get_main_queue(), ^{
        [theTableView reloadData];
    });
});

However, this would cause some weird things to happen in the UI. New managed objects would render blank cells, deleted managed objects would cause cells to overlap, etc.

However, I found that if I did this, everything would render correctly.:

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [theTableView endUpdates];
    });
}

What I wanted to know is, why is this necessary? Since it fires as a result of [theTableView reloadData], why isn't it automatically included in the main queue? I thought maybe that it was because I didn't call it explicitly. In that case, do I have to wrap all my functions similarly?

Kamaros
  • 4,536
  • 1
  • 24
  • 39
  • This really isn't necessary. What do you mean by "Since it fires as a result of [theTableView reloadData]"? Since what fires? I suspect there may be problems with the code handling the JSON responses and the way it is interacting with CoreData. Perhaps you can share more code? – Mike Pollard Oct 09 '13 at 12:02
  • 1
    Could it be that you store the results in Core Data in the background thread, but use the main managed object context instead of creating a separate context? Managed object contexts are not thread safe! – Martin R Oct 09 '13 at 12:08
  • The Core Data and JSON interactions work perfectly. The changes when I call [theTableView reloadData] then cause the NSFetchedResultsControllerDelegate protocol method "controllerDidChangeContent" to fire as a result of the updates to the NSManagedObjectContext. What is not working without wrapping [theTableView endUpdates] in a dispatch_async block is the UI. For example, things like cell overlap happen when I delete a managed object so that the old cell is still present, but the cells below it know to move up. This results in cell overlap, so the labels from both cells are still visible. – Kamaros Oct 09 '13 at 14:18

2 Answers2

3

I assume that you use the main NSManagedObjectContext from a separate thread, which is not allowed. For a background import, you have to create a separate managed object context, import the objects in that context, and then save/merge the changes to the main context.

One possibility is to use a child context of the main managed object context:

NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc]
                         initWithConcurrencyType:NSPrivateQueueConcurrencyType];
childContext.parentContext = mainManagedObjectContext;
[childContext performBlock:^{
    // Perform JSON requests.
    // Save objects to childContext.

    NSError *error;
    BOOL success = [childContext save:&error];
}];

A context of the "private concurrency type" creates its own queue/thread, so that the performBlock is executed in the background (and you need/must not create a queue yourself).

Saving childContext merges the changes up to the parent mainManagedObjectContext. This should be sufficent for the fetched results controller to get notified of the changes, and update the table view.

See Core Data Release Notes for OS X v10.7 and iOS 5.0 for more information about managed object context concurrency, and nested contexts.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I'm pretty sure my managed object context and core data handling is fine. The fetched results controller is already notified of the changes. It's just that without wrapping [theTableView endUpdates] in a dispatch_async block, it's almost as if the animations aren't complete. New managed objects will properly create new cells, but the labels won't appear until the cell is highlighted. Deletions cause other cells to move, but won't remove the old cell until I navigate to another view. – Kamaros Oct 09 '13 at 14:25
  • 1
    @Kamaros: Which managed object context do you use to store the object in Core Data on the background thread? – Martin R Oct 09 '13 at 14:28
  • I have a managed object context property on my app delegate. To be honest, I'm not completely sure if that part of my methodology is remotely thread safe, but it hasn't failed me yet. – Kamaros Oct 09 '13 at 14:53
  • 1
    @Kamaros: NSMangedObjectContext is *not thread safe*. You must not use the main context from a background thread. It might *seem* to work, but it doesn't. In your case (I assume) the fetched results controller (which is tied to the main context) is notified on the background thread, and then tries to update the table view. That is again a problem because UI updates must be done on the main thread. – Martin R Oct 09 '13 at 15:00
  • Working on it. I'll get back to you after I finish making those changes (the main class that handles server integration is a 3000 line singleton, so it may take a few days). – Kamaros Oct 09 '13 at 21:52
0

was

 [theTableView beginUpdates]

written anywhere in the code? Try removing it. I suspect the UI weird stuff is happening because beginUpdates is already asynchronous itself (not blocking the main thread) for funky UITableView animations to take place.

Joel Tay
  • 83
  • 1
  • 9