In the previous episode, we exposed the root cause of the crash we ran into earlier. The solution is versioning the data model.
Restoring the Data Model
Before we create a new version, we need to restore the current data model to its original state. Select Notes.xcdatamodeld and remove the colorAsHex attribute from the Category entity.

Because we reverted the data model to its original state, the application should no longer crash.
Don't worry about any errors that pop up. Because we removed the colorAsHex attribute, the compiler complains that the Category class doesn't have a property named colorAsHex. We fix that in a few moments.
Adding a Data Model Version
It's time to create a new data model version. With the data model selected, choose Add Model Version... from Xcode's Editor menu. Name the version Notes 2 and base the data model version on Notes. It's the only option available. You should always base a new version of the data model on the previous data model.

Notice that a small triangle has appeared on the left of the data model in the Project Navigator. You can click the triangle to show the list of data model versions.

You may have noticed that a green checkmark is added to Notes.xcdatamodel. This indicates that Notes.xcdatamodel is the active data model version. If we were to run the application, Core Data would continue to use the original data model version.
But that's not what we have in mind. Before we make any changes, select Notes.xcdatamodeld, not Notes.xcdatamodel. Open the File Inspector on the right and set Model Version to Notes 2, the data model version we just added.


Notice that the green checkmark has moved from Notes.xcdatamodel to Notes 2.xcdatamodel.

Because we haven't run the application yet, we can still modify the new data model version without running into compatibility issues. Select Notes 2.xcdatamodel and add the colorAsHex attribute to the Category entity. Don't forget to uncheck the Optional checkbox and set Default Value to white.

Run the application to see if we solved the incompatibility problem we ran into earlier. Are you still running into a crash? To make changes to the data model, we've added a new data model version. We also marked the new data model version as the active data model version.
What we haven't told Core Data is what it should do if it runs into an incompatibility issue. We need to tell it to perform a migration.
Performing Migrations
I already told you that a persistent store is tied to a particular version of the data model. It keeps a reference to the unique identifier of the data model. If the data model changes, we need to tell Core Data how to migrate the data of the persistent store to the new data model version.
There are two types of migrations:
- lightweight migrations
- and heavyweight migrations
Heavyweight migrations are complex and you should try to avoid them whenever possible. Heavyweight migrations are an advanced topic and they're not covered in this series. Lightweight migrations are much easier because Core Data takes care of the heavy lifting.
To add support for lightweight migrations to the CoreDataManager class, we need to make a minor change. Remember that addPersistentStore(ofType:configurationName:at:options:) accepts a dictionary of options as its last parameter. To add support for migrations, we pass in a dictionary of options with two keys:
NSMigratePersistentStoresAutomaticallyOption- and
NSInferMappingModelAutomaticallyOption
CoreDataManager.swift
let options = [
NSMigratePersistentStoresAutomaticallyOption : true,
NSInferMappingModelAutomaticallyOption : true
]
By setting the value of NSMigratePersistentStoresAutomaticallyOption to true, we instruct Core Data to automatically perform a migration if it detects an incompatibility. That's a good start.
If the value of NSInferMappingModelAutomaticallyOption is set to true, Core Data attempts to infer the mapping model for the migration based on the data model versions of the data model.
This automatically brings us to the question "What is a mapping model?" A mapping model defines how one version of the data model relates to another version. For lightweight migrations, Core Data can infer the mapping model by inspecting the data model versions. This isn't true for heavyweight migrations and that's what makes heavyweight migrations complex and tedious. For heavyweight migrations, the developer is responsible for creating the mapping model.
With this in mind, we can update the implementation of the do clause of the do-catch statement in the CoreDataManager class. This is what the updated implementation looks like.
CoreDataManager.swift
private lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
...
do {
let options = [
NSMigratePersistentStoresAutomaticallyOption : true,
NSInferMappingModelAutomaticallyOption : true
]
// Add Persistent Store
try persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType,
configurationName: nil,
at: persistentStoreURL,
options: options)
} catch {
fatalError("Unable to Add Persistent Store")
}
return persistentStoreCoordinator
}()
Run the application to see if the solution works. If you don't run into a crash, then Core Data successfully migrated the persistent store to the new data model version.
Keep It Lightweight
Whenever you make a change to a data model, you need to consider the consequences. Lightweight migrations carry little overhead. Heavyweight migrations, however, are a pain. Really. Avoid them if possible.
How do you know if a data model change requires a lightweight or a heavyweight migration? You always need to test the migration to be sure. That said, Core Data is pretty clever and is capable of migrating the persistent store most of the times without your help.
Adding or removing entities, attributes, and relationships are no problem for Core Data. Modifying the names of entities, attributes, and relationships, however, is less trivial for Core Data. If you change the cardinality of a relationship, then you signed up for a wild ride.
Plan, Plan, Plan
Every form of persistence requires planning. I can't stress enough how important this phase of a project is. If you don't invest time architecting the data model, chances are you run into problems you could have avoided.
It's fine to make incremental changes to the data model as your application grows, but once your application is in the hands of users you need to make sure they don't lose their data due to a problematic migration. And always test migrations before shipping a new version of your application.
Migrations are an important aspect of Core Data because most applications grow and need to make changes to the data model at some point. Data model changes and migrations aren't hard, but they require attention and testing.