Lightweight Migrations in Core Data Tutorial
Learn how to keep your data models up-to-date with this Core Data migrations tutorial! By Aaron Douglas.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Lightweight Migrations in Core Data Tutorial
20 mins
A Lightweight Migration
In Xcode, select the UnCloudNotes
data model file if you haven’t already. This will show you the Entity Modeler in the main work area. Next, open the Editor menu and select Add Model Version…. Name the new version UnCloudNotesDataModel v2
and ensure UnCloudNotesDataModel
is selected in the Based on model
field. Xcode will now create a copy of the data model.
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. If you forget this step, selecting the top level UnCloudNotesDataModel.xcdatamodeld file will perform any changes you make to the original model file. You can override this behavior by selecting an individual model version, but it’s still a good idea to make sure you don’t accidentally modify the original file.
In order to perform any migration, you want to keep the original model file as it is, and make changes to an entirely new model file.
In the File Inspector
pane on the right, there is a selection menu toward the bottom called Model Version.
Change that selection to match the name of the new data model, UnCloudNotesDataModel v2
.
Once you’ve made that change, notice that the little green check mark icon in the project navigator has moved from the previous data model to the v2 data model:
Core Data will try to first connect the persistent store with the ticked model version when setting up the stack. If a store file was found, and it isn’t compatible with this model file, a migration will be triggered. The older version is there to support migration. The current model is the one Core Data will ensure is loaded prior to attaching the rest of the stack for your use.
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 custom NSValueTransformer
to convert from binary bits to a UIImage
and back again. Just such a transformer has been provided for you in ImageTransformer
. In the Data Model Inspector on the right of the screen, look for the Value Transformer field, and enter ImageTransformer. Next, in the Module field, choose Current Product Module.
UnCloudNotes
or Current Product Module
depending on what your drop down provides) to allow the class loader to find the exact code you want to attach.
The new model is now ready for some code! Open Note.swift and add the following property below displayIndex
:
@NSManaged var image: UIImage?
Build and run the app. You’ll see your notes are still magically displayed! It turns out lightweight migrations are enabled by default. This means every time you create a new data model version, and it can be auto migrated, it will be. What a time saver!
Inferred mapping models
It just so happens Core Data can infer a mapping model in many cases when you enable the shouldInferMappingModelAutomatically
flag on the NSPersistentStoreDescription
. 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:
- Deleting entities, attributes or relationships
-
Renaming entities, attributes or relationships using the
renamingIdentifier
- Adding a new, optional attribute
- Adding a new, required attribute with a default value
- Changing an optional attribute to non-optional and specifying a default value
- Changing a non-optional attribute to optional
- Changing the entity hierarchy
- Adding a new parent entity and moving attributes up or down the hierarchy
- Changing a relationship from to-one to to-many
- Changing a relationship from non-ordered to-many to ordered to-many (and vice versa)
As you 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 of nil 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.
Image attachments
Now the 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.
Open Main.storyboard and find the Create Note
scene. Underneath, you’ll see the 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:
Next, open AttachPhotoViewController.swift and add the following method to the UIImagePickerControllerDelegate
extension:
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info:
[UIImagePickerController.InfoKey: Any]) {
guard let note = note else { return }
note.image =
info[UIImagePickerController.InfoKey.originalImage] as? UIImage
_ = navigationController?.popViewController(animated: true)
}
This will populate the new image property of the note once the user selects an image from the standard image picker.
Next, open CreateNoteViewController.swift and replace viewDidAppear(_:)
with the following:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let image = note?.image else {
titleField.becomeFirstResponder()
return
}
attachedPhoto.image = image
view.endEditing(true)
}
This will display the new image if the user has added one to the note.
Next, open NotesListViewController.swift and update tableView(_:cellForRowAt):
with the following:
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let note = notes.object(at: indexPath)
let cell: NoteTableViewCell
if note.image == nil {
cell = tableView.dequeueReusableCell(
withIdentifier: "NoteCell",
for: indexPath) as! NoteTableViewCell
} else {
cell = tableView.dequeueReusableCell(
withIdentifier: "NoteCellWithImage",
for: indexPath) as! NoteImageTableViewCell
}
cell.note = note
return cell
}
This will dequeue the correct UITableViewCell
subclass based on the note having an image present or not. Finally, open NoteImageTableViewCell.swift and add the following to updateNoteInfo(note:)
:
noteImage.image = note.image
This will update the UIImageView
inside the NoteImageTableViewCell
with the image from the note. Build and run, and choose to add a new note:
Tap the Attach Image
button to add an image to the note. Choose an image from your simulated photo library and you’ll see it in your new note:
The app uses the standard UIImagePickerController
to add photos as attachments to notes.
If you’re using a device, open 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.