Core Data Model Merging
May 4th, 2011I haven’t used Apple’s Core Data framework all that much, but I’m trying to dabble more in it with newer projects where I don’t rely as much on legacy data storage, or am willing to take the hit of migrating from those legacy persistence models.
As a developer, the obvious upside to using Core Data is Apple’s powerful framework manages most of the nitty-gritty details of managing the … well, the data your app uses both in memory and on disk. But giving up precise control over the storage and mangement of this data also becomes maddening when you run into problems: you’re not directly responsible for the management, so you’re less likely to know exactly what is going wrong.
I ran into a problem recently having to do with versioning of core data model schemas, and how good a job the frameworks do or do not do of automatically updating existing data that a customer may have on disk. In recent years Apple has made it increasingly easy to make minor changes to your model schema and having the frameworks handle the migration easily. Apple calls this trivial technique lightweight migration, and when it works well, it works very well. To modify your existing data model, you just add a new “version” of your xcdatamodel file, and add a few options flags when creating your persistent store:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; [myPSC addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error];
This actually works quite well for simple addition or removal of entity attributes, but you might be forgiven for assuming its completely freaking broken if you run into a problem I did, having to do with a stale “.mom” file being left in the built application’s bundle in the iOS simulator and on iOS devices.
Essentially, if you’ve been building your project up to now with a single, unversioned xcdatamodel file, and you convert it to a versioned xcdatamodel file, it will start producing a new “.momd” file in your application bundle, and stop producing the older “.mom”. But because of a bug in the way Xcode installs the application binary in the simulator and devices, it will leave existing files from previous builds where they were in the bundle.
If, like many folks, you use the convenient [NSManagedObjectModel mergedModelFromBundles:nil] method to initialize your managed object model, this will, to use a technical term, bite you in the ass. The problem is Core Data looks at the .mom and the .momd files as equally viable, and attempts to merge them into some cohesive whole. When it discovers they both redundantly describe the same entities, it blows a gasket with a runtime error like “Can’t merge models with two different entities named ‘MyEntity'”
I did some googling around and discovered that the simplest solution is to simply delete the iOS app from the device or simulator, ensuring that the next time you install, you get a pristine, clean copy of the app without the troubling “.mom” file. But there’s a huge problem with that: in deleting the app you also delete its associated data files, and thus are left in a position where you aren’t actually testing the ability of Core Data to effectively migrate from your previously versioned data.
On the simulator, it’s no problem to dive in and trash the problematic, stale file. You can find the application binary somewhere in:
[Home] -> Library -> Application Support -> iTunes Simulator
Once you’ve deleted the “.mom” file you can test with your new model and everything hopefully works great.
On the device, it’s not as easy (I think) to get in there and do pinpoint editing of the application contents, but I still wanted to test my changes against the real data on my device. I was also starting to get paranoid about the possibility of an App Store update at some point also causing a stale “.mom” file to be left in an app bundle, and wreaking havoc. So the 100% safe solution to make sure the one-true “.momd” file is consulted when building the Core Data object model, is to point directly at it:
NSString *momdPath = [[NSBundle mainBundle] pathForResource:@"MyModel" ofType:@"momd"]; NSURL *momdURL = [NSURL fileURLWithPath:momdPath]; managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momdURL];
Hopefully seeding this solution into my blog will make it available for folks searching on similar failures in their Core Data projects.