How to create Core Data database from multiple model files
This is useful if you want to split your app into modules but still keep and manage only single database inside the main target.
Published: Aug. 19, 2020 Sponsored I want a Press KitWe are used having single .xcdatamodel
file in our app that contains all the definitions for entities and we use that to setup Core Data stack when the app launches. Something along these lines:
persistentContainer = NSPersistentContainer(name: "Model")
persistentContainer.loadPersistentStores { storeDescription, error in
if let error = error {
print("Unresolved error \(error)")
fatalError()
}
}
Where the name "Model" corresponds to the file Model.xcdatamodel
. This almost makes it seem like that it is required to have this exact setup. But .xcdatamodel
is just a description how the database scheme should look like and we can actually use multiple models to create our own database.
If you have a big app and want to split it into self-containing modules, you can use this approach to split your .xcdatamodel
file into multiple files and then use these to create single database.
I am talking about structured showed in the image below. Each "Module" has its own .xcdatamodel
file and Core Data entity classes. And we want our main app to manage the database and not have databases inside each module.
NSPersistentContainer
has this alternative initializer:
NSPersistentContainer(name: String, managedObjectModel: NSManagedObjectModel)
This means you can pass it instance of NSManagedObjectModel
yourself and it will use that instead of finding .xcdatamodel
file in your main bundle using the provided name. In this case the name will be used only for the name of the database.
How do you create NSManagedObjectModel
?
There are couple of options one is a init
which takes URL
of the .xcdatamodel
file. We can also leverage available static methods, one of which is:
NSManagedObjectModel.mergedModel(from: [Bundle]?)
This is really powerful and exactly what we want. This in simplified terms means something like: "Give me instances of Bundle
and I will search those bundles for .xcdatamodel
files, load them and merge them into single NSManagedObjectModel
instance".
And this instance can be then used to create an instance of NSPersistentContainer
.
There is a small caveat and that is that we need our own NSPersistentContainer
subclass to disable default behaviour which only looks for model files in the main bundle. This is the recommended way by Apple and it does not entail any custom implementation:
class PersistentContainer: NSPersistentContainer { }
With this in place, we can prepare code to load model from our bundles:
let model = NSManagedObjectModel.mergedModel(from: bundles)!
persistentContainer = PersistentContainer(name: "Database", managedObjectModel: model)
The rest of the setup is the same. You can check my approach to setting Core Data stack in previous post.
Instance of Bundle
can be created by specifying a class. So you can pick any Core Data entity class that lives in the bundle you want and create Bundle
like this:
Bundle(for: User.self)
Alternative would be to create empty marker class for each bundle for better semantics. Something along these lines:
public final class UsersBundle {
private init() { }
// marker class
}
Bundle(for: UsersBundle.self)
Once this is ready, you can init your database in AppDelegate
func prepareDatabase() {
let modelBundles: [Bundle] = [
Bundle(for: User.self),
Bundle(for: Document.self)
]
Database.shared.prepare(loadFromBundles: modelBundles)
}
And finally this is how my prepare
method looks like:
func prepare(loadFromBundles bundles: [Bundle]?) {
let model = NSManagedObjectModel.mergedModel(from: bundles)!
persistentContainer = PersistentContainer(name: "Database", managedObjectModel: model)
persistentContainer.loadPersistentStores { storeDescription, error in
if let error = error {
print("Unresolved error \(error)")
fatalError()
}
}
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
context.automaticallyMergesChangesFromParent = true
}
And done!
This approach has one great advantage. If you have an existing app with database, you can still split it with this approach into modules and if you use the same name it will just work just like before the split.
How to easily split .xcdatamodel
file
You don't have to recreate all the entities again once you decide to split your .xcdatamodel
file into multiple files. Behind the scenes it is simple XML file, so you can open it in text editor and with a bit of care cut out entities and paste them into new files. Once Xcode opens those in its own editor you can be confident that it worked.
The root node is <model>
and its children are <entity>
and also <elements>
where each <element>
represents entity in a canvas. I think you don't even need to move these elements. I think even if this takes more than one try it is way faster than manually creating the entities from scratch.
Unlike storyboard files, these model files make sense when you open their source and aren't complicated.
Uses: Xcode 11 & Swift 5