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.
May 4th, 2011 at 5:00 pm
Thanks for the help. I’ve been putting off updating my iPhone app because it will require tweaking the data model, and it makes me nervous!
May 4th, 2011 at 11:25 pm
After reading a book about Core Data and playing with it a bit I’d say: use only when necessary and think about it twice. I know Apple would like to deprecate SQL Lite in favor of Core Data but…
May 5th, 2011 at 6:21 pm
The way I recommend testing an upgrade on a device is to make an ad-hoc build of v2 of your app, and install it through iTunes on something that has v1 on it. This will totally replace the v1 app on the device, but won’t touch the app’s data (NSUserDefaults, files in it’s sandbox, etc.) It’s the closest way I know of to simulate what happens when you upgrade an app from the App Store.
May 5th, 2011 at 6:29 pm
Thanks for the tip, Vincent!
May 6th, 2011 at 1:32 pm
Great post. I ran into this exact problem myself, and solved it with the same solution.
Creating a merged Managed Object Model is particularly dangerous if you’re using libraries that include mom files of their own (Webtrends).