CoreData

NSFetchedResultsController and Core Data Managed Object Updates

In the process of working on an iPhone project for a client, I came across an interesting problem. I was using Core Data to store domain related information. This data is being pulled from a server, parsed, and persisted on a background thread. The main thread of the application presents a list of information based on what is persisted. In this application, I chose to use an NSFetchedResultsController to manage querying the underlying Core Data store and implemented the standard UITableView delegate methods. I won’t go into detail on how to do that here, but there are many good write-ups out there, including this very good one: How to use NSFetchedResultsController.

I was updating the underlying data store in a background thread with its own managed object context, as is the standard practice. As a result, the NSFetchedResultsController’s managed object context was unaware of the changes to the data store. This was easily fixed by subscribing to the NSManagedObjectContextDidSaveNotification notification in my table view controller class.

[[NSNotificationCenter defaultCenter] addObserver:self 
                                         selector:@selector(refreshData:) 
                                             name:NSManagedObjectContextDidSaveNotification 
                                           object:nil];

The notification that gets fired includes all the information about the changes to the underlying data store (Inserts, Updates, and Deletes). The nice thing here is that there is a convenience method on the managed object context for merging these changes in, as shown in the snippet below:

- (void) refreshData:(NSNotification *)notif {
    [[[self fetchedResultsController] managedObjectContext] mergeChangesFromContextDidSaveNotification:notif];
}

On the surface, this worked beautifully. Once the changes are merged in, the NSFetchedResultsController delegate methods start firing. This is where my struggle occurred. All the proper delegate methods fired correctly, but the underlying result set for the NSFetchedResultsController still had the old data. I combed through the documentation, code samples, etc. and it seemed that I was doing everything correct. I came up with some hack arounds, but they were too ugly…and just wrong. I knew there had to be something I was missing.

I could see that the object that I updated in my main thread’s managed object context was a faulted object (see Apple’s description of a Faulted object here if you aren’t familiar with this concept). What I didn’t realize is that when accessing attributes of a faulted object, the store will retrieve the underlying data from the previously fetched data first, then from the underlying data store. How long the previously fetched data is used is based off of the staleness interval attribute of the managed object context. BTW, this defaults to infinity 🙂

So, the addition of 1 line of code when creating my NSFetchedResultsController to set the staleness time interval to 0 fixed everything.

[context setStalenessInterval:0];

Now my NSFetchedResultsController delegate methods were able to update the table view with the updated data as expected.

For those of you following the tutorial referenced above, one change is necessary if you use a sectioned table view by setting the sectionNameKeyPath when initializing the NSFetchedResultsController.

In the didChangeObject delegate method, the NSFetchedResultsChangeMove case should look like this:

case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
                             withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] 
                             withRowAnimation:UITableViewRowAnimationFade];
            break;

Otherwise you will get an error under certain use cases (e.g. changing/removing the last item in a section so the section gets deleted).