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 0x6b80390
2012-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 0x6d55040
2012-01-25 21:45:31.174 CBF[16911:1310b] -[__NSArrayM _addPersistentStore:identifier:]: unrecognized selector sent to instance 0x6d59370
2012-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'