Customizing Core Data Migrations

If you are using Core Data, need to change your database scheme but Core Data cannot infer the changes on its own.

And you don’t want to dig into the Core Data Programming Guide, you have come to the right place.

Custom migration is quite a powerful tool and you can perform all kinds of transformations, e.g., change relationships or merge entities. In this guide, only changes of the attribute’s type are discussed, but this might be a good starting point for other kinds of migrations, too.

(This guide refers to Xcode Version 4.5.2 (4G2008a) and iOS >=5.)

TABLE OF CONTENTS

  1. Versioned Core Data Model
  2. Lightweight Migrations
  3. Example Scenario
  4. Custom Mapping Models
  5. Custom Migration Policy
  6. Conclusion

1. VERSIONED CORE DATA MODEL

You can skip this section if you are already familiar with versioned Core Data models.

If you simply change your model it will be incompatible with the Core Data store used to create it and Core Data wont be able to open it. Therefore you need to add a model version:

  1. Select your Core Data model file in Project Navigator
  2. Choose Add Model Version.. from Editor menu item
  3. The Based on model field should indicate your previous model version

If you do this for the first time, this turns the original document into a file package that groups both versions of the model, each represented by an individual .xcdatamodel file:

One for the original Core Data model and one for your updated model.

Choose the latter in Project Navigator and change your model as required. Now tell Core Data to use your new model:

  1. Select the container Core Data model in Project Navigator
  2. In File Inspector (View -> Utilities -> File Inspector) choose the latest version in the Versioned Core Data Model dropdown. Check that the green checkmark switched from the old to the new version in the File Inspector.

2. LIGHTWEIGHT MIGRATIONS

Core Data needs to know how to map the entities and properties from a source model to the destination model. For the following cases, Core Data can infer the changes automatically, which is referred to as lightweight migration:

  • Addition of new attributes
  • Removal of attributes
  • Changing a non-optional attribute to an optional
  • Changing an optional attribute into a non-optional, and defining a default value
  • Renaming an entity or property (if the renaming identifier is set appropriately)
  • Please refer to Core Data Model Versioning and Data Migration Programming Guide for the complete list.

In order to activate lightweight migrations, you have to pass the following options

objectivecNSDictionary *options = @{
  NSMigratePersistentStoresAutomaticallyOption : @YES,
  NSInferMappingModelAutomaticallyOption : @YES
};

when invoking:

objectivecaddPersistentStoreWithType:configuration:URL:options:error:

If Core Data cannot infer the mapping model automatically, it will not be able to open the persistent store. Resetting the simulator or reinstalling your app will do the trick. But if your app was already released on the App Store you want to guarantee your customers a smooth update process. That is where custom mapping models come into play.

3. EXAMPLE SCENARIO

Let us assume we have a view controller that shows comments. Therefore, we have got an entity named Comment consisting of three attributes: authortext and createdAt, all of type String.

Furthermore, the server delivering the data sends the creation date in a custom format that requires further processing.

We released our app to the App Store, just to realize afterwards that it would have been more efficient to store the transformed timestamp in our entity instead of doing the transformation whenever we access this attribute. Thus, let us add a new model version, update the type of the timestamp attribute from String to Date and set this model version to be the current version.

Since Core Data will not be able to infer the mapping model on its own, let us create a custom mapping model.

4. CUSTOM MAPPING MODELS

To create a custom Core Data mapping model:

  1. Open the new file dialog File -> New -> File
  2. Choose the Mapping Model template ( Core Data -> Mapping Model)
  3. Choose the source and target version of your versioned Core Data model

IMPORTANT NOTE:

If your Core Data model evolved over several versions since your last App Store release, you have to choose the version used in your App Store release as the source in the mapping model! Otherwise the mapping model will not be applied when updating from that release.

5. CUSTOM MIGRATION POLICY

The custom mapping model contains an entity mapping called CommentToComment, which contains attribute mappings prepopulated to perform a simple copy and paste of all attribute values. Since we want to perform a custom transformation on the createdAt attribute, change its attribute mapping to:

objectivecFUNCTION($entityPolicy, "dateFromTimestamp:" , $source.createdAt)

This tells the migration manager to call dateFromTimestamp:

on the entity migration policy object and to pass the createdAt value of the source entity. Last, we need to ensure Core Data finds our custom transformation method:

  1. Create CommentTransformationPolicy class as a subclass of NSEntityMigrationPolicy
  2. Define and implement the transformation method dateFromTimestamp:
objectivec+ (NSDateFormatter *)dateFormatter{
   static NSDateFormatter *sharedDateFormatter = nil;
   if (!sharedDateFormatter){
      sharedDateFormatter = [[NSDateFormatter alloc] init];
      [sharedDateFormatter setDateFormat:@"yyyy-MM-dd'T'HHmmssZZ"];
   }
   return sharedDateFormatter;
}

- (NSDate *)dateFromTimestamp:(NSString* )timestamp{
   return [[[self class] dateFormatter] dateFromString:s];
}
  1. Register this class as custom entity migration policy in the mapping model:
  • Select the mapping model file in the project navigator
  • Select the CommentToComment entry in Entity Mappings
  • Fill out CommentTransformationPolicy in Custom Policy field of the mapping model inspector (View -> Utilities -> Show Data Model Inspector)

6. CONCLUSION

We now have a working custom migration that smoothly updates the app’s database. This way, you save time writing your own methods to detect whether a migration is actually necessary. If you have got any questions or suggestions feel free to leave a comment or tweet me @c_gretzki.

Special thanks to Martin Winter for pointing me to the mystical

objectivec_FUNCTION($entityPolicy, "dateFromTimestamp:"; , $source.createdAt)_

syntax ;)!