I have a multi-threaded app that uses Core Data. I’ve been seeing a lot of crashes on startup, and various bizarre error messages. However, sometimes it works fine! I have never seen a crash on my own iPhone4, but it does crash on other devices. I think I’ve figured out the issue, but am not sure how to best solve it.
When the app starts, I use GCD to download data from the web and update core data on a background thread. On the main thread, I continue with setting up table views, and one of these commands triggers the NSManagedObjectContext getter. I’m using pretty much the standard template code, so as it is the first time this is run, the usual lazy instanciation code creates the NSPersistentStoreCoordinator.
However the background thread is creating a new NSManagedObjectContext for it’s own use. This includes getting the NSPersistentStoreCoordinator as follows:
// Create a new NSManagedObjectContext for this thread
NSManagedObjectContext *threadContext = nil;
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
threadContext = [[NSManagedObjectContext alloc] init];
[threadContext setPersistentStoreCoordinator:coordinator];
NSMergePolicy *mergePolicy = [[NSMergePolicy alloc] initWithMergeType:NSMergeByPropertyObjectTrumpMergePolicyType];
[threadContext setMergePolicy:mergePolicy];
} else {
NSLog(@"Error - No NSPersistentStoreCoordinator");
}
I’m 90% sure that issue is that both threads are getting to the NSPersistentStoreCoordinator code at the same time. Both threads believe the object is nil, therefore create the object. This causes issues for the first thread to get there as the ivar suddenly points to the wrong place. At that point bad things happen!
So how best to solve this? I have temporarily fixed the issue by adding a sleep(1) to the background thread 🙂 but I’m not sure this is really the best solution! I have tried changing the NSPersistentStoreCoordinator properties so that they are atomic, but this hasn’t helped. Should I wrap each of the standard getters in @synchronise to mutex lock them? I haven’t had time to try this yet, but it should stop the same code being run by both threads. Or is there a better way?
Thoughts?
Thanks Craig
======================================
For info, here are some of the errors and crashes I have seen. These are pretty random, but it may help others find this post in the future.
2012-01-18 22:19:57.586 CBF[10468:11d03] -[NSSQLModel
_addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6b803902012-01-18 22:19:57.595 CBF[10468:11d03] * Terminating app due to
uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[NSSQLModel
_addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6b80390’2012-01-19 16:58:06.671 CBF[11738:fe03] -[__NSCFDictionary
_hasPrecomputedKeyOrder]: unrecognized selector sent to instance 0x6d550402012-01-25 21:45:31.174 CBF[16911:1310b] -[__NSArrayM
_addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6d593702012-01-25 21:45:31.175 CBF[16911:1310b] * Terminating app due to
uncaught exception ‘NSInvalidArgumentException’, reason: ‘-[__NSArrayM
_addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6d59370’
Why not just remove the lazy instantiation, and instead create the coordinator immediately when the app starts?