Latest Posts:

Core Data Migrations Tutorial: Lightweight Migrations

When you create a Core Data app, you design an initial data model for your app. However, after you ship your app inevitably you’ll want to make changes to your data model. What do you do then – you don’t want to break the app for existing users!
You can’t predict the future, but with Core Data, you can migratetoward the future with every new release of your app. The migration process will update data created with a previous version of the data model to match the current data model.
This Core Data migrations tutorial discusses the many aspects of Core Data migrations by walking you through the evolution of a note-taking app’s data model. You’ll start with a simple app with only a single entity in its data model, and use a lightweight migration to update the user’s data model. The full chapter in the Core Data by Tutorials book will guide you through more complex migration examples, as you add more features and data to the app.
Let the journey begin!
Note: This tutorial assumes some basic knowledge of Core Data and Swift.

When to migrate

When is a migration necessary? The easiest answer to this common question is “when you need to make changes to the data model.”
However, there are some cases in which you can avoid a migration. If an app is using Core Data merely as an offline cache, then when you update the app, you can simply delete and rebuild the data store. This is only possible if the source of truth for your user’s data isn’t in the data store. In all other cases, you’ll need to safeguard your user’s data.
That said, any time it’s impossible to implement a design change or feature request without changing the data model, you’ll need to create a new version of the data model and provide a migration path.

The migration process

When you initialize a Core Data stack, one of the steps involved is adding a store to the persistent store coordinator. When you encounter this step, Core Data does a few things prior to adding the store to the coordinator.
First, Core Data analyzes the store’s model version. Next, it compares this version to the coordinator’s configured data model. If the store’s model version and the coordinator’s model version don’t match, then Core Data will perform a migration, when enabled.
Note: If migrations aren’t enabled, and the store is incompatible with the model, Core Data will simply not attach the store to the coordinator and specify an error with an appropriate reason code.
To start the migration process, Core Data needs the original data model and the destination model. It uses these two versions to load or create a mapping model for the migration, which it uses to convert data in the original store to data that it can store in the new store. Once Core Data determines the mapping model, the migration process can start in earnest.
Migrations happen in three steps:
  1. First, Core Data copies over all the objects from one data store to the next.
  2. Next, Core Data connects and relates all the objects according to the relationship mapping.
  3. Enforce any data validations in the destination model. Core Data disables destination model validations during the data copy.
You might ask, “If something goes wrong, what happens to the original source data store?” With nearly all types of Core Data migrations, nothing happens to the original store unless the migration completes without error. Only when a migration is successful, will Core Data remove the original data store.

Types of migrations

In my own experience using Core Data, I’ve found there are a few more migration variants than the simple distinction between lightweight and heavyweight. Below, I’ve provided the more subtle variants of migration names, but these names are not official categories by any means. I’ll start with the least complex form of migration and end with the most complex form.
Lightweight migrations
A lightweight migration is Apple’s term for the migration with the least amount of work involved on your part. Simply enable a couple of flags when setting up a Core Data stack, and the migration happens automatically. There are some limitations on how much you can change the data model, but because of the small amount of work required to enable this option, it is the ideal setting.
Manual migrations
Manual migrations involve a little more work on your part. You need to specify how to map the old set of data onto the new set, but you get the benefit of a more explicit mapping model file to configure. Setting up a mapping model in Xcode is much like setting up a data model, with similar GUI tools and some automation.
Custom manual migrations
This is level 3 of the migration complexity index. You still use a mapping model, but add to that custom code with the ability to also specify custom transformation logic on data. In this case, custom entity transformation logic involves creating an NSEntityMigrationPolicy subclass and performing custom transformations there.
Fully manual migrations
Fully manual migrations are for those times when even specifying custom transformation logic isn’t enough to fully migrate data from one model version to another. In this case, custom version detection logic and custom handling of the migration process are necessary. In this chapter, you’ll use set up a fully manual migration to update data across non-sequential versions, such as jumping from version 1 to 4.
In this tutorial you’ll focus on lightweight migrations because they are the easiest and most common type of migrations. Let’s get started!

Getting started

First, download the starter project for this tutorial and open it in the latest public version of Xcode.
Build and run the app in the iPhone simulator. You’ll see an empty list of notes:
Starter project
Tap the plus (+) button in the top-right corner to add a new note. Add a title (there is default text in the note body to make the process faster) and tap Create to save the new note to the data store. Repeat this a few times so that you have some sample data to migrate.
Back in Xcode, open the UnCloudNotesDatamodel.xcdatamodeld file to show the entity modeling tool in Xcode. The data model is simple—just one entity, a Note, with a few attributes.
Modeling tool in Xcode
You’re going to add a new feature to the app: the ability to attach a photo to a note. The data model doesn’t have any place to persist this kind of information, so you’ll need to add a place in the data model to hold onto the photo. But you already added a few test notes in the app—how can you change the model without breaking the existing notes? It’s time for your first migration!

A lightweight migration

With the entity modeler open, open the Editor menu and select Add Model Version…. Call the new version UnCloudNotesDataModel v2 and select UnCloudNotesDataModel in the “Based on Model” field. Xcode will now create a copy of the data model.
Note:You can give this file any name you want. The sequential v2, v3, v4, et cetera naming helps you tell the versions apart easily.
This step will create a second version of the data model, but you still need to tell Xcode to use the new version as the current model. In the File Inspector pane on the right, find the option toward the bottom called Model Version. Change that selection to match the name of the new data model,UnCloudNotesDataModel v2:
Core_Data_by_Tutorials_v_1_2
Once you’ve made that change, notice in the project navigator that the little green check mark icon has moved from the previous data model to the v2 data model:
003_Migration
Core Data will load the ticked version when setting up the stack. The older version is there to support migration—it’s hard to migrate to a new model without knowing the old one!
Make sure you have the v2 data model selected and add an image attribute to the Note entity. Set the attribute’s name to image and the attribute’s type to Transformable.
Since this attribute is going to contain the actual binary bits of the image, you’ll use a customNSValueTransformer to convert from binary bits to a UIImage and back again. Just such a transformer has been provided for you as ImageTransformer. In the Value Transformer Name field in the Data Model Inspector on the right of the screen, enter UnCloudNotes.ImageTransformer.
004_Transformer
The new model is now ready for some code! Open Note.swift and add a property to match the new attribute:
@NSManaged var image: UIImage?
Build and run, and you’ll see your notes have disappeared! In the Xcode console, you’ll see some error text related to the CoreDataStack object:
context: 
modelName: UnCloudNotesDataModelmodel: [Note: ]
coordinator: 
storeURL: file:///Users/YOURNAME/Library/Developer/CoreSimulator/Devices/A24A4E68-D616-4F63-8946-652164EE5E53/data/Containers/Data/Application/9921B2DD-D0FD-4330-90F9-A2F44CC9899A/Library/Application%20Support/UnCloudNotes.sqlite
store: nil
The store file is still around (storeURL in the log above), but since it’s incompatible with the new v2 model, Core Data couldn’t attach it to the persistent store, so store is still nil.
Core Data can automatically update your store if all you’ve done is add a new property like this. These are called lightweight migrations.

Enabling lightweight migrations

To enable lightweight migrations, you need to set two flags on initialization. The stack in this app lives in an object imaginatively titled CoreDataStack, which you’ll modify to do this.
Open CoreDataStack.swift and add a property to the class:
var options: NSDictionary?
Right now, you’re setting up the persistent store with no options for default behavior. You’ll use the options dictionary to set the necessary flags.
Next, update the initializer to match the following:
init(modelName: String, storeName: String,
 options: NSDictionary? = nil) {
  self.modelName = modelName
  self.storeName = storeName
  self.options = options
}
In setting the default value of options to nil, the old method signature remains valid and you have the additional choice of passing in the options dictionary.
Find the coordinator computed property and change the initialization of the persistent store as follows:
store = coordinator.addPersistentStoreWithType(
  NSSQLiteStoreType,
  configuration: nil,
  URL: storeURL,
  options: self.options)
There’s just a small change here to pass in the extra options when creating the stack.
Open NotesListViewController.swift and change the CoreDataStack lazy initialization statement to use the lightweight migration options:
lazy var stack : CoreDataStack = CoreDataStack(
  modelName:"UnCloudNotesDataModel",
  storeName:"UnCloudNotes",
  options:[NSMigratePersistentStoresAutomaticallyOption: true,
           NSInferMappingModelAutomaticallyOption: true])
The NSMigratePersistentStoresAutomaticallyOption is what tells Core Data (theNSPersistentStoreCoordinator, actually) to start a migration if the persistent store model isn’t compatible with the current data model. Core Data will handle all the details, from finding the source model to creating the destination store file, and all the steps in between.
The NSInferMappingModelAutomaticallyOption is the other half of what makes a lightweight migration possible. Every migration requires a mapping model. Here’s an analogy: If you’re traveling from a known place on Earth to somewhere unknown, you’ll want a map to tell you where to go. The mapping model is that guide.
It just so happens that Core Data can infer a mapping model in many cases. That is, Core Data can automatically look at the differences in two data models and create a mapping model between them. For entities and attributes that are identical between model versions, this is a straightforward data pass through mapping. For other changes, just follow a few simple rules for Core Data to create a mapping model. In the new model, changes must fit an obvious migration pattern, such as:
  1. Deleting entities, attributes or relationships;
  2. Renaming entities, attributes or relationships using the renamingIdentifier;
  3. Adding a new, optional attribute;
  4. Adding a new, required attribute with a default value;
  5. Changing an optional attribute to non-optional and specifying a default value;
  6. Changing a non-optional attribute to optional;
  7. Changing the entity hierarchy;
  8. Adding a new parent entity and moving attributes up or down the hierarchy;
  9. Changing a relationship from to-one to to-many;
  10. Changing a relationship from non-ordered to-many to ordered to-many (and vice versa).
Note: Check out Apple’s documentation for more information on how Core Data infers a lightweight migration mapping:
https://developer.apple.com/library/Mac/DOCUMENTATION/Cocoa/Conceptual/CoreDataVersioning/Articles/vmLightweightMigration.html
As you can see from this list, Core Data can detect, and more importantly, automatically react to, a wide variety of common changes between data models. As a rule of thumb, all migrations, if necessary, should start as lightweight migrations and only move to more complex mappings when the need arises.
As for the migration from UnCloudNotes to UnCloudNotes v2, the image property has a default value ofnil since it’s an optional property. This means Core Data can easily migrate the old data store to a new one, since this change follows item 3 in the list of lightweight migration patterns.
Build and run, and your old notes have returned! Core Data has migrated the data store automatically using an inferred mapping model.
The non-nil value for the store: entry in the logs is a nice confirmation that the migration actually happened and that there’s now an NSPersistentStore object representing the store.
005_Output
Congratulations—you’ve completed your first data migration!

Image attachments

Now that data is migrated, you need to update the UI to allow image attachments to new notes. Luckily, most of this work has been done for you, so you can quickly get to the interesting part. :]
Open Main.storyboard and go to the Create Note scene. Just underneath, you’ll see a Create Note With Images scene that includes the interface to attach an image.
The Create Note scene is attached to a navigation controller with a root view controller relationship. Control-drag from the navigation controller to the Create Note With Images scene and select the root view controller relationship segue. This will disconnect the old Create Note scene and connect the new, image-powered one instead:
006_Storyboard
Open AttachPhotoViewController.swift and add the following method to the class:
func imagePickerController(picker: UIImagePickerController,
 didFinishPickingMediaWithInfo info: [String: AnyObject]) {
  if let note = note {
    note.image = info[UIImagePickerControllerOriginalImage] as? UIImage
  }
  self.navigationController?.popViewControllerAnimated(true)
}
This will populate the new image property of the note once the user selects something from the standard image picker.
Open CreateNoteViewController.swift and replace viewDidAppear with the following:
override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)
  if let image = note?.image {
    attachedPhoto.image = image
    view.endEditing(true)
  } else {
    titleField.becomeFirstResponder()
  }
}
This implementation will display the new image if the user has added one.
Next, open NotesListViewController.swift and update tableView(_:cellForRowAtIndexPath) with the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let note = notes.fetchedObjects?[indexPath.row] as? Note
  let identifier = note.image == nil ? "NoteCell” : "NoteCellImage"
 
  if let cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath) as NoteTableViewCell {
    cell.note = note
    return cell
  }
  return UITableViewCell()
}
This will set the correct cell identifier based on whether an image is present in the note. If there is an image, you also need to populate the image view in the cell. Open NoteTableViewCell.swift and add the following lines after the code that sets the creation date label’s text in updateNoteInfo():
if let image = note?.image {
  noteImage.image = image
}
Build and run, and choose to add a new note:
007_New_Note
Tap the Attach Image button to add an image to the note. Choose an image and you’ll see it in your new note:
008_Attach_Image
The app uses a standard UIImagePickerController to add photos as attachments to notes.
Note: To add your own images to the Simulator’s photo album, drag an image file onto the open Simulator window. Thankfully, the iOS Device Simulator now comes with a library of photos ready for your use. :]
If you’re using a device, go to AttachPhotoViewController.swift and set the sourceType attribute on the image picker controller to .Camera to take photos with the device camera. The existing code uses the photo album, since there is no camera in the Simulator.

Where To Go From Here?

Here is the final example project from the above tutorial.
The full chapter in the Core Data by Tutorials book follows up this Core Data migrations tutorial with a series of more complex migrations. In the full book chapter, you’ll walk through creating a mapping model with entity and attribute mappings from one version to the next. You’ll also learn about custom migration policies, and how to migrate non-sequential data models.
I hope you enjoyed this Core Data migrations tutorial, and if you have any questions or comments, please going the discussion thread below.
Share on Google Plus

About Divya

This is a short description in the author block about the author. You edit it by entering text in the "Biographical Info" field in the user admin panel.
    Blogger Comment
    Facebook Comment

0 comments:

Post a Comment