Core Data for iOS: Part 3 – Migrating the data to a new data model

As developers, we constantly try to improve our apps by adding features and optimizing the code. Sometimes these changes have an impact on the data model. Assuming we use Core Data, what options do we have when we need to modify the data structure of an existing app?

Because it’s not the first release, we know existing users have opened the app at least once, which probably has created the persistent data store based on the initial version of the data model. Also, the users probably created data that is expected to be preserved after updating to the latest version of the app.

We have two problems to deal with when we make changes to the data model of an app that’s already on the market:

  • how to propagate the changes to the structure of an existing persistent store, which is the physical representation of data, most times in the form of a database?
  • how to migrate the existing data to the new data model, making sure that none of it is lost in the process?

At first sight, these tasks seem daunting. Fortunately, Core Data comes with a migration mechanism that make things almost transparent in most cases. Although there are complex situations where the migration has to be performed manually, this article focuses on the most common types of data model changes that can be automatically propagated by Core Data.

The sample code is available for download on GitHub. It reuses the code from the Part 2 of the Core Data blog post series, which in turn uses the data model created in the Part 1.

Data model versioning

The Core Data model contains the definition of the data entities with their attributes and relationships with other entities. It is embedded in a .xcdatamodeld file inside the Xcode source file tree.

If we make changes to the data model and want the migration from the old model to be automatic, we must create a new version of the model definition.

This can be done by selecting the .xcdatamodeld file and clicking on the Add Model Version button in the Editor menu, then providing the name for the new model version:

new data model version name

Once the new version with the contents copied from the original version, it appears in the source file tree under the .xcdatamodeld file, alongside the original version. Because the two versions coexist in the Xcode project, we have to specify which one is actually used by the app. We specify the version to be used by selecting the .xcdatamodeld file and changing the current Model version in the File inspector:

changing the current data model

Now it’s time to make the changes into the second version of the data model. I kept the modifications relatively small, but they are relatively common:

  • removed the address attribute from the Publisher entity
  • added the Address entity
  • added the firstLetter transient attribute to the Publisher entity
  • created a to-one relationship between the Publisher and the Address entities

Transient attributes and NSManagedObject subclass

Transient attributes are a special type of managed object property, because they are not persisted in the store as the standard attributes are. They are dynamically calculated, generally based on the other stored attributes.

The purpose of the firstLetter attribute added to the Publisher entity is to provide the initial letter of the publisher’s name stored in the name attribute. It will be used by the NSFetchedResultsController to group the publishers in sections according the initial letter of their name.

Because the values of the transient attributes are calculated, we must write some code to provide these values dynamically.

We first create a NSManagedObject subclass for the Publisher entity. Xcode contains a template for NSManagedObject subclasses, letting us select the model version and the entity to create a subclass for:

nsmanagedobject sublcass

The public interface of the subclass contains a property for each attribute of the entity. These properties are declared @dynamic in the implementation file because the accessors are generated automatically by Core Data, providing read / write access to the underlying data in the persistent store.

From now on, we will use the Publisher class to handle the publisher core data objects, instead of the generic NSManagedObject class. The subclass enables us to write custom accessors and override those generated dynamically by Core Data.

We override the getter for the firstLetter attribute to return the initial letter of the name:

Light data migration

Core Data is able to automatically handle data model modifications similar to those we’ve just made. It changes the structure of the persistent store to match the new model and migrates the data from the old to the new structure.

This operation is called light migration because the framework compares the two versions of the data model and infers, without any external help, the changes to be made to the persistent store. When the modifications to the data model have a deeper impact on the existing data structure, we have to write custom code to perform the migration to the new version of the data model.

To enable to light migration, we have to provide two special options when we add the persistent store to the store coordinator:

  • NSMigratePersistentStoresAutomaticallyOption: forces Core Data to try an automatic migration when it detects changes to the data model
  • NSInferMappingModelAutomaticallyOption: causes Core Data to attempt mapping between the old and the new version of the data model and migrate the data if it successfully aligns the two distinct versions.

The code that adds these two options looks like this:

That’s all we have to do.
When the app is launched for the first time, the SQLite file populated in Part 2 of the series is copied from the project bundle to the app’s Documents directory and the data is automatically migrated to the new data model.

The visual proof is the Publishers table view, which is now split in several sections according to the initial letter of the publisher’s name. We provide the attribute name to be used for grouping the managed objects to the NSFetchedResultsController, and it creates the sections when the data is queried:

A few considerations on the heavy migration

The heavy migration (or manual migration) is necessary if Core Data is not able to figure out on its own how to transpose the data from the old to the new version of the data model. This type of migration requires more work from the developer, and also a lot more time to execute.

In a heavy migration, we have to create two Core Data stacks, one for the old data model and one for the new version. Then each managed object is individually loaded from the old persistent store and transferred to the new one. This process can be somewhat simplified if we create a mapping model to tell Core Data how to align the two versions of the data model.

If you cannot avoid a manual migration, you should start by reading Apple’s documentation on the topic.

Conclusion

In most scenarios, making changes to a Core Data model and migrating existing data to the new version is surprisingly simple.

For those special situations when a manual migration is necessary, Xcode provides tools to make things a bit easier for the developer. But custom code must often be written to deal with the edge cases.

 

Catalin Rosioru