Using Core Data Concurrently and Reliably
cocoa-touch, core-data, ios, iphone, nsmanagedobjectcontext
Solution
Pre-iOS 5, we've usually had two `NSManagedObjectContexts`: one for the main thread, one for a background thread. The background thread can load or delete data and then save. The resulting `NSManagedObjectContextDidSaveNotification` was then passed (as you're doing) to the main thread. We called `mergeChangesFromManagedObjectContextDidSaveNotification:` to bring those in to the main thread context. This has worked well for us.
One important aspect of this is that the `save:` on the background thread blocks until after the `mergeChangesFromManagedObjectContextDidSaveNotification:` finishes running on the main thread (because we call mergeChanges... from the listener to that notification). This ensures that the main thread managed object context sees those changes. I don't know if you need to do this if you have a parent-child relationship, but you did in the old model to avoid various kinds of trouble.
I'm not sure what the advantage of having a parent-child relationship between the two contexts is. It seems from your description that the final save to disk happens on the main thread, which probably isn't ideal for performance reasons. (Especially if you might be deleting a large amount of data; the major cost for deletion in our apps has always happened during final save to disk.)
What code are you running when the controllers appear/disappear that could be causing core data trouble? What kinds of stack traces are you seeing the crash on?
Problem
I'm building my first iOS app, which in theory should be pretty straightforward but I'm having difficulty making it sufficiently bulletproof for me to feel confident submitting it to the App Store. Briefly, the main screen has a table view, upon selecting a row it segues to another table view that displays information relevant for the selected row in a master-detail fashion. The underlying data is retrieved as JSON data from a web service once a day and then cached in a Core Data store. The data previous to that day is deleted to stop the SQLite database file from growing indefinitely. All data persistence operations are performed using Core Data, with an `NSFetchedResultsController` underpinning the detail table view. The problem I am seeing is that if you switch quickly between the master and detail screens several times whilst fresh data is being retrieved, parsed and saved, the app freezes or crashes completely. There seems to be some sort of race condition, maybe due to Core Data importing data in the background whilst the main thread is trying to perform a fetch, but I'm speculating. I've had trouble capturing any meaningful crash information, usually it's a SIGSEGV deep in the Core Data stack. The table below shows the actual order of events that happen when the detail table view controller is loaded: ``` Main Thread Background Thread viewDidLoad Get JSON data (using AFNetworking) Create child NSManagedObjectContext (MOC) Parse JSON data Insert managed objects in child MOC Save child MOC Post import completion notification Receive import completion notification Save parent MOC Perform fetch and reload table view Delete old managed objects in child MOC Save child MOC Post deletion completion notification Receive deletion completion notification Save parent MOC ``` Once the AFNetworking completion block is triggered when the JSON data has arrived, a nested `NSManagedObjectContext` is created and passed to an "importer" object that parses the JSON data and saves the objects to the Core Data store. The importer executes using the new `performBlock` method introduced in iOS 5: ``` NSManagedObjectContext *child = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType]; [child setParentContext:self.managedObjectContext]; [child performBlock:^{ // Create importer instance, passing it the child MOC... }]; ``` The importer object observes its own MOC's `NSManagedObjectContextDidSaveNotification` and then posts its own notification which is observed by the detail table view controller. When this notification is posted the table view controller performs a save on its own (parent) MOC. I use the same basic pattern with a "deleter" object for deleting the old data after the new data for the day has been imported. This occurs asynchronously after the new data has been fetched by the fetched results controller and the detail table view has been reloaded. One thing I am not doing is observing any merge notifications or locking any of the managed object contexts or the persistent store coordinator. Is this something I should be doing? I'm a bit unsure how to architect this all correctly so would appreciate any advice.