I haven’t posted anything for a while now, and after hours of trying to find a solution to my problem, I thought I should share. So here we go.
The problem
Your nice and shiny iOS app is supposed to have two data components : User data and Seed data. For example, you want to have some (seeded) list of postcodes. The size of data is too big to be shipped with your app, and we assume that the model is too complex to be just filled by your application at runtime from a downloaded csv/txt file.
So, you start thinking that hey, you’ll generate a sqlite database (persistent data store as they say), put it on a server and have your app download and use it.
You can either duplicate the whole stack (NSManagedObjectContext
, NSPersistentStoreCoordinator
and NSManagedObjectModel
) or, according to apple :
You typically use configurations if you want to store different entities in different stores. A persistent store coordinator can only have one managed object model, so by default each store associated with a given coordinator must contain the same entities. To work around this restriction, you can create a model that contains the union of all the entities you want to use. You then create configurations in the model for each of the subsets of entities that you want to use. You can then use this model when you create a coordinator. When you add stores, you specify the different store attributes by configuration. When you are creating your configurations, though, remember that you cannot create cross-store relationships.
Well, that’s pretty much all the doc you’ll get from apple. There are a few mentions of this problem there or there but not in a clear enough form for me. So, here’s how it works…
Models
We want to have two separate models (because that’s the way it is, or because your seed data can be used in apps that have nothing to do with this one). Let’s create two models in xcode (I’ll be using dumb Model, Conf suffixes just to help understanding) :
- Our UserModel model has one entity
Rating
with two attributes :postcode
(anNSString
) andrating
(an integer). - Our PostCodesModel model has two entities
Postcode
(with apostcode
attribute, and let’s say some location and address attributes), andCounties
that which postcode belongs to which county.
We assume that we have already generated a ‘PostCodes.sqlite’ using normal core data stuff, based on
PostCodesModel
only. That’s the seed file we’ll want to download.
How it’ll work
Since we can only have one model for one NSPersistentStoreCoordinator
, we’ll need to merge our two models.
We’ll create one configuration in each model, with the entities of this model.
We’ll then add two NSPersistentStore
, one per .sqlite
file, and with a configuration set up to make sure that core data
uses the correct store for the correct entities.
Adding configurations.
We add one configuration named UserConf to our UserModel model, and drag & drop all our entities to it. We do the same with a PostCodesConf configuration for our postcodes model.
Creating a merged model
We change our -(NSManagedObjectModel*)managedObjectModel
to the following :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Our new model should now have our three entities, and two configurations : UserConf for our Rating
entity, and PostcodesConf for the two others.
Creating a persistent store
That’s where the difficulty is. One would think that just calling addPersistentStoreWithType:configuration:URL:options:error
once per .sqlite file with the correct configuration would be enough. It isn’t. When you add the first one (our postcodes data),
it finds that the store (our downloaded sqlite file) wasn’t created with this model : we only used the PostCodesModel
model to create
our .sqlite file, not our merged model that we are now using.
We could then think of using migration, but then the migrated model used by core data when migrating won’t have our PostCodesConf configuration anymore. I think that’s a bug of core data. The solution is to :
- Add the
postcodes.sqlite
persistent store, without a configuration, but with the auto-migration options. Core data will figure out that his sqlite file has just some missing tables (the UserModel tables). This store needs to be writable for the migration to work properly. - Remove our newly created persistent store (he had a default configuration, we want him to use UserConf.
- Add the same persistent store again, which has now the correct metadata and can be used with our merged model.
- The user data sqlite file is usually fine, because it’s created with the merged model anyway when the app run. If not, you could think of using this plugin architecture
Here is what I end up with :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
Conclusion
Well, adding a bit more information, or a sample somewhere could have been helpful, apple.