views:

326

answers:

2

My app starts by presenting a tableview whose datasource is a Core Data SQLite store. When the app starts, a secondary thread with its own store controller and context is created to obtain updates from the web for data in the store. However, any resulting changes to the store are not notified to the fetchedresults controller (I presume because it has its own coordinator) and consequently the table is not updated with store changes. What would be the most efficient way to refresh the context on the main thread? I am considering tracking the objectIDs of any objects changed on the secondary thread, sending those to the main thread when the secondary thread completes and invoking "[context refreshObject:....] Any help would be greatly appreciated.

+1  A: 

In your NSFetchedResultsController handling the table, register in viewDidLoad or loadView for a notification:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(contextDidSave:) name:@"ContextDidSave" object:nil];

When the secondary thread is ready with the new data, simply save the context as usual, and then post the notification:

[[NSNotificationCenter defaultCenter] postNotificationName:@"ContextDidSave" object:managedObjectContext];

The notification will be handled in your NSFetchedResultsController using the following method:

EDIT: modified the method below taking correctly into account multi-threading, after an insightful discussion with bbum.

- (void)contextDidSave:(NSNotification *)notification
{

    SEL selector = @selector(mergeChangesFromContextDidSaveNotification:);
    [[[[UIApplication sharedApplication] delegate] managedObjectContext] performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];

}

For UI update, it can be done automatically using the NSFetchedResultsController delegate methods.

Finally, remember to add in the dealloc method of the NSFetchedResultsController the following:

[[NSNotificationCenter defaultCenter] removeObserver:self];
unforgiven
Thanks, great answer.
Run Loop
What up w/the dealloc() and loadView() stuff? That isn't the standard way to indicate an Objective-C method; use `-dealloc` and `-loadView` to reduce confusion.
bbum
Wait. This answer isn't correct; it'll cause the reloading of the data to happen in the wrong thread.
bbum
I have used the performSelectorOnMainThread:withObject:waitUntilDone: as suggested as this solved the notification delivery issue. But my principal problem of merging store changes accross threads was answered. As I have implemented the fetchedresultscontroller delegates, I also don't need to explicitly reload the table.
Run Loop
A: 

Unforgiven's answer doesn't handle threading correctly. In particular, the documentation states (emphasis mine):

If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, generally require this behavior, but even for those that do not, keeping this behavior on the main thread has the advantage of simplifying the logic for managing your user interface.

A notification observer will be fired on whatever thread the notification was posted upon in the first place. Thus, you can't call NSTableView's reloadData directly from the notification posted by a background thread.

There is no need to use notifications at all. In your background thread, when ready to update the user interface, use any of a number of mechanisms to reload the data in the main thread -- in the thread that manages the main event loop & user interface.

[tableView performSelectorOnMainThread: @selector(reloadData)
           withObject: nil waitUntilDone: YES];

You can also use Grand Central Dispatch or NSOperation to do something similar.

bbum
The main problem here is not updating the UI (it may even be done automatically by the NSFetchedResultsController delegate methods), it is merging the updated objects retrieved in the background thread. The answer is based on how Core Data handles insertion, deletion or editing of NSManagedObjects in different threads. Another possibility is doing SEL selector =@selector(mergeChangesFromContextDidSaveNotification:); [[[[UIApplication sharedApplication] delegate] managedObjectContext] performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES]; in the notification method.
unforgiven
Yes -- my answer doesn't really address the OP's question, but more the issue of threading. Your suggestions are, in fact, a better answer. Fix your answer above to deal with the threading issue and make these suggestions and I'll happily delete my inferior answer.
bbum