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
App Store

We 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.

AppModules diagram

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

Filip Němeček profile photo

WRITTEN BY

Filip Němeček @nemecek_f@iosdev.space

iOS blogger and developer with interest in Python/Django. Want to see most recent projects? 👀

iOS blogger and developer with interest in Python/Django. Want to see most recent projects? 👀