2.9 Data Migration and Versions
It's inevitable that as an app grows and matures, there will be changes to the data model. In this lesson, I'll show you what to do when your data model changes with a new version of your app.
1.Introduction2 lessons, 04:06
2.Core Data9 lessons, 1:04:07
3.Conclusion1 lesson, 00:53
2.9 Data Migration and Versions
Hi, and welcome back to Get Started with Core Data. In this final episode, we are going to have a look at what happens when your data model changes, and you need to migrate it to a new version. During the lifetime of your application, your data model will inevitably get outdated. Requirements change and new features will require different attributes and entities. As soon as you have released a version on the app store, you will need to deal with migrations. So as a first step, let's add the index we wanted to add in the last lesson, in a new model version. First of all, I need to create a new version via the Editor menu under Add Model Version. I'm going to call this PubQuizzer 2, which is based under original PopQuizzer model. Now we can make our changes. On the quiz, we can add an index to the date since we're sorting by this attribute. On the question object, let's add a completely new attribute called index to sort the questions of the fetched results controller. I'm giving it a default and also a minimum value of 0. We could also add some indexes on the question entity itself like the quiz, since we are using it as in a predicate. Here you will have to separate the values with commas. When we have a look at the data model and expand it, you can see the green check mark on the original model version file. This is the current version. To use our version two model, we need to set it as current. This can be done in the file inspector while having the data model selected. When launching the app, this will trigger a migration when having an older model. There are two types of migrations in core data. The first one is an automatic migration, which is generally referred to as a lightweight migration, and the other one is a manual migration. Lightweight migrations are used for simple changes to the data model, like adding, renaming, or removing an attribute, entity or relationship. It is important that core data can infer the mapping from the old model to the new one. To enable this type of migrations, we have to set some options on the persistent store coordinator when adding the store. The first is NS migrate persistent stores automatically option which tells core data to run migrations if it needs to. And the second one is NS infer mapping a model automatically option which enables lightweight migrations. These migration options can be set when adding a persistent store. We also have to do this when resetting the store. It isn't always possible to migrate from one version to another automatically. Sometimes you want to use existing fields to populate new fields, although everything else what the automatic. Or Core Data simply can't make sense of the changes to the data model because they are too complex. In this case, we need to aid Core Data with the process. One way to do that is to use a policy that helps with a conversion. Let's create such a file called Question To Question To Policy. We want to use it to fill the index attribute of the order that is currently used by the order of the relationship between question and quiz. It is a Swift class that inherits from NS Entity Migration Policy. Here we need to override the function called create destination instances for source instance. It does exactly what it says. It takes a source instance from the old data model and creates a destination instance with the new data model. Before doing anything, let's call super, then let's get discretion for the new data model from the manager. It was created by the default implementation of the policy which we triggered with the super call. Here, we need to provide the mapping name and an array of source instances. Since this could potentially return multiple instances, we need to call first since we know there will be only one destination instance in our case. Since the object are of the generic NSManagedObject class, I need to use value for key to fetch any properties. In this case, quiz.questions. I'm also forcefully casting it to an array of NSManagedObject. With that data, I can now set the value of index to the index of the question in the quiz relationship. Well, actually it isn't that easy, because I used the destination instance, which isn't aware of the relationships quite yet. We need to fetch the quiz from the source instance of course and then use this object to fetch the questions as an ordered set. Since we have a set not an array, we need to call index of object and use the source instance instead of the quiz object. To use this policy, let's add a model mapping file. This allows you to define what goes where during a migration, helping call data if it can then vary it automatically. You have to choose a source and a target data model this mapping is referring to. As you can see, you can use target expressions to use while mapping the attributes from the source. I won't get into those because they are quite powerful and can get complex when you have a lot of changes going on. A simple example, however would be the conversion of a temperature from Fahrenheit to Celsius, where you would put source the temperature minus 32 divided by 1.8 into the expression. In our case, the mapping will be inferred automatically. So we wouldn't normally need it but I want to set a property on QuestionToQuestion in the data model inspector, and use a custom policy, which is the one we created before. During migration, our policy will be used to convert the question entity. In the question table field controller, we can now change our naive implementation for ordering to a new one that uses the index. I'm using a for loop to update all the questions with the new index after moving. Since we won't have too many questions, I'm not limiting the loop to just the ones that changed. Which would be easy to do though, because we know the old and new row path and just need the ones between. Right now, setting the index isn't possible, because we need to generate new versions of our managed object model. We can do that in the editor and select the model version we want to generate, and choose the correct folder and group. Make sure you don't select use primitive data types to get an NS number instead of an integer 16. Since we already got a question class, only the Core Data properties extension was updated. With these changes made Xcode doesn't complain anymore. Let's also change the text label to reflect the index value. Before we can see how we did, I need to change the sorter scripter from quiz to index. So let's build and run. Since there are no error messages, immigration must have gone smoothly. If you look at the questions, you can see that the indexes are already filled, perfect. Now let's try reordering them. This also works, great! A final note on migrations though, if you need custom migrations, it can be tedious if you need to create them for every possible previous version. To avoid that, you can check if you have the current model version and if not, tell Core Data to migrate to the next higher version, meaning from one to two and then two to three. This means you will be migrating your data multiple times in a single step but it ensures that you don't miss any data transformations you rely on within your app.