Core Data Heavyweight Migration
We all know what core data is and how powerful it is to manage local database.
But as the project starts growing, the attributes of entities or sometimes even the entire schema of entities might change.
To deal with the such changes, core data provides another powerful feature named “Core Data versioning and migration”
So What is Core Data versioning and migration?
Core data allows you to migrate data from one version of the data model to another.
There are two type of migrations you can perform. Lightweight and Heavyweight.
If changes in the schema are not very complex then you can simply use lightweight migration.
To perform lightweight migration, all you have to do is, set property ‘shouldMigrateStoreAutomatically’ and ‘shouldInferMappingModelAutomatically’ true.
As the name suggest, core data will automatically infer the mapping model and perform data migration.
You can use lightweight migration, when
- New entity/attribute/relationship added
- Any entity/attribute/relationship removed
- Name or type of any entity/attribute/relationship changed
Now lets come to the topic of the blog .
What is heavyweight migration and when do we perform it?
So first of all what does apple say about heavyweight migration?
“Use heavyweight (manual) migration in rare cases when changes to the data model exceed the capabilities of lightweight migration.”
Well thats all they say about it.
We perform heavyweight migration when we have either normalised or generalised our database.
In such cases, lightweight migration wont help us. We will have to manually map attributes of old data model to the new data model, and doing so is called heavyweight migration.
Other use cases of heavyweight migration could be, when you want to perform some serious customisation on data.
Lets see, How the Heavyweight migration is done..
You can find code sample project Here
So what happened is, I have an app which is used by Swedish(people of Sweden) and by the finns(people of Finland).
Initially when I designed the data model architecture, I just kept one entity with the name “InternationalUser” which was used to contain data of all the user.
But now due to project requirement, I need to have two different entities. One for Sweden and one for Finland and have to replace one city from both( Sweden and Finland) with another city.
So lets see how its done.
Lets Start with creating new project. Make sure “Use Core Data” is checked
Lets design schema of the first version of data model, which will later replaced by new version.
In third step
- I inserted some dummy data in entity “InternationalUser”.
- Created table view and populated with dummy data.
So our apps looks something like this.
The text in grey is users name and text in black is their city.
Need not to tell about image.
To migrate from one version to new version, WE NEED A NEW VERSION.
So lets create one.
To create new model version, You first open the existing data model window, then go to “Editor” and the click on “Add Model Version”
So you will see a window something like this.
First field is for the name of new model version.
And second field is tell, new version is preceded by which data model version.
Now lets design new schema. As I said earlier, now I want two different entities for Sweden and Finland users.
So I will create, two entities with name FinlandUser and SwedenUser.
These two entities have almost same attribute as the earlier “InternationalUser” entity except one “userCountry”. We removed “userCountry” in our new data model version .
So our FinlandUser and SwedenUser entities will look something like this.
This is probably the only step, which requires some coding. finallyyy.
So lets create a swift class with name SwedenUserMapping.
This class will be inheriting NSEntityMigrationPolicy.
NSEntityMigrationPolicy is the class, which allows us to take full control over migration process. We can do n number of things using this class, like writing custom migration logic, mapping 1 attribute with N number of attribute and vice versa, running complex logic on data etc.
PS: I could have used single class to perform mapping and migration of both the entities, but for better clarity, I created two separated NSEntityMigrationPolicy class, one for Sweden User and one for Finland user.
The only method we need to override is the createDestinationInstances.
So our SwedenUserMapping class looks something like this
As per the apple document, createDestinationInstances(forSource:in:manager:) creates the destination instance(s) for a given source instance.
here source refers to the source entity(old data model), mapping refers to entity mapping and manager is the instance of NSMigrationManager.
NSMigrationManager performs the data migration using given mapping.
So What I am doing in my custom logic is,
- Checking if sIntance.Entity is InternationalUser or not. If yes move ahead.
- Storing attribute value into local variables
- If user country is Sweden, then create an object of NSEntityDescription Class for entity SwedenUser into destination context(new data model).
- Set all the values.
- As I mentioned earlier, I need to replace one city from both (Sweden and Finland) to new city. So in our SwedenUserMapping class, I am replacing city Trosa with Nora.
I wrote smilier logic in our FinlandUserMapping class.
Remember the createDestinationInstances method will only be call once to perform data migration. If data migration is completed successfully, It will never be called again.
Now we are done with the coding as well.
The core and most important element of data migration process is the Mapping Model.
We need to have a mapping model, Which will do mapping of attributes of older version to attributes of new version.
To create a mapping model, press command + N -> Scroll down to Core Data section -> Click on Mapping Model
Next Window you will see is
Select the source of Mapping, which we definitely be your older version of data model.
Clicking on next will navigate you to below window.
Now select the target of your mapping model. Your target will be the new version of data model.
So you must be seeing something like this.
Two Entity Mapping with same name as your entities name in new version data model.
On your right hand side there will be Entity Mapping Inspector.
Currently the source field has value “Invalid Value”.
Clicking on it, We show all entities of old data model version.
You select the entity of old data model, which you want to map with this entity of new data model.
As soon as you select the source entity, core data will automatically map all fields and It will even change the entity mapping name.
Have another look at Entity Mapping Inspector and you will find one field with name “Custom Policy”.
This field is to tell core data that “Do we have our own mapping policy?”
Yes We have. The one we just created, the “SwedenUserMapping”.
So just write its name in the Custom Policy field.
Do not forget to add project module name with ‘.’ , as prefix.
Do same with the Finland Entity Mapping as well.
Next thing we need to do is, double click on any of the model version and you will see an inspector window on your right.
Here you will see an option with name Model Version. Just select your new data model version. In my case its HeavyweightMigrationV2.
Now open your app delegate file and look for lazy property persistentContainer and override its body with below peace of code
We are setting shouldInferMappingModelAutomatically to true, which means, we want core data to infer mapping automatically.
So now we are all done with heavyweight migration thing. lets run the app and perform data migration.
Just to confirm that our migration completed successfully, Lets create two method to load data from entities “SwedenUser” and “FinlandUser” respectively.
Now populate fetched data into our existing table view (The same, which we used to show InternationalUser data.)
So Thats all, We have successfully performed Heavyweight Migration. Cheerss…