iPhone Core Data "Automatic Lightweight Migration"

iPhone Core Data Automatic Lightweight Migration


To recap/Full guide:

  1. Before making any change, create a new model version.

    In Xcode 4: Select your .xcdatamodel -> Editor -> Add Model Version.

    In Xcode 3: Design -> Data Model -> Add Model Version.

    You will see that a new .xcdatamodel is created in your .xcdatamodeld folder (which is also created if you have none).

  2. Save.

  3. Select your new .xcdatamodel and make the change you wish to employ in accordance with the Lightweight Migration documentation.

  4. Save.

  5. Set the current/active schema to the newly created schema.

    With the .xcdatamodeld folder selected:

    In Xcode 4: Utilities sidebar -> File Inspector -> Versioned Core Data Model -> Select the new schema.

    In Xcode 3: Design > Data Model > Set Current Version.

    The green tick on the .xcdatamodel icon will move to the new schema.

  6. Save.

  7. Implement the necessary code to perform migration at runtime.

    Where your NSPersistentStoreCoordinator is created (usually AppDelegate class), for the options parameter, replace nil with the following code:

    [NSDictionary dictionaryWithObjectsAndKeys:
    [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
    [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]
  8. Run your app. If there's no crash, you've probably successfully migrated :)

  9. When you have successfully migrated, the migration code (step 7) can be removed. (It is up to the developer to determine when the users of a published app can be deemed to have migrated.)

IMPORTANT: Do not delete old model versions/schemas. Core Data needs the old version to migrate to the new version.

Enabling core data lightweight migration in Swift 3

You do it using NSPersistentStoreDescription, which is where all those options moved to in the Swift 3 updates. Do this before the call to loadPersistentStores:

let description = NSPersistentStoreDescription()

description.shouldInferMappingModelAutomatically = true
description.shouldMigrateStoreAutomatically = true

container.persistentStoreDescriptions = [description]

Core Data - mixing lightweight and custom migration

Two methods are written below:

  1. I personally like this method since it's just genius IMO, you can tell from the detail of my explanation why I'm so excited about this method. This method has been written in Marcus Zarra's core data second edition book. It's an implementation for an automatic Progressive migration process for both heavy and lightweight use.

  2. A method I came up with for a possible implementation after the OP asked if there was a way to avoid having to use mapping models for each migration upgrade

Method 1
This is where Progressive Migration comes in. You create mapping models for each version upgrade that will deal between two model versions. A mapping model will tell the migration manager how to map an old version of the data store to the new version of the data store. So a mapping model requires a source and a destination.

You then have as many mapping models required that will cover all the version upgrade increments. So if you have 4 versions, youll have 3 mapping models that will aid the transition between each version upgrade. In the end youll have them named: V1toV2MappingModel, V2to3MappingModel, V3toV4MappingModel.


In each mapping model you can then toggle the options to let the migration manager know how you wish to migrate. Whether its via a custom policy or a normal copy migration. So you say that for v1 to v2 you require a light weight migration, simply select the appropriate mapping model, you then use the model/mapping editor to make the desired connections/mappings between old attributes to new attributes and if its a custom migration required, then go to the right mapping model, choose the product entity mapping that you wish to customise the migration for, then in the entitymapping inspector you'll see that you can apply a custom migration policy, just copy the name of your migration policy say for example MigrationPolicyV2toV3 since its that particular version that you wanted to have customised.

Sample Image

So in the image above you can see on the left hand side in the name of the mapping model that it's for Version 1 to Version 2. Notice that the attribute mapping for the ProductToProduct entity mapping is empty - in the middle. This is because if you look on the right of the image in the entity mapping inspector where it says Custom Policy, I've put the name of my migration policy. This then lets the migration manager know (during the migration process) how to map the attributes and values from the source to the destination - version 1 to version 2 - and it knows this because of the migration policy inputted. This also causes the value type to be changed to custom. letting you know its going to be a custom migration when it comes to the ProductToProduct entity mapping.

Then you have to define in your migration policy which will determine how you wish to copy the values over. This is where you do your custom stuff.

Sample Image

As you can see from the image above, its the custom migration policy I've set for the ProductToProduct entity mapping. You'll notice that I'm not actually doing anything custom, all this could have been done without the migration policy and could have been achieved by simply doing a copy migration (lightweight migration) by adjusting a few values in the entityMapping inspector and adjusting the Attribute mapping values in the image before. I only did all of this migration policy custom stuff just as an exercise so that I can learn and be prepared for the future just INCASE I ever needed to do a heavy migration. Better learn it now than later hey ;)

That's it for doing custom stuff. I suggest you read up on the following apple developer reference on NSEntityMigrationPolicy and the methods required to do more custom stuff so that you know how to have full control throughout the migration process whenever a particular revision upgrade requires some or full custom migration.


And for any custom/heavyweight migrations - where in my case I use a migration policy so that I can do some custom code stuff during the migration from V2 to V3 in my data store - then you create something called a 'migration policy' so that THAT mapping model will adhere to the custom migration rules your specify.

And you simply apply all the appropriate transitions/mappings for each mapping model so that the migration manager knows how to upgrade from one store to the next.

All you need then is some recursive code that will look at the existing store's meta data to determine whether it's compatible with the most current version, and if its not, it will then do a recursive migration for you, automatically, following the rules from each mapping model as it upgrades through the versions until the store is up to date with the current version.

So this way you can accommodate all users with any version and have them brought up to speed to the current version's store. So if a user is at version 1, it will recursively go from V1 to V2, then migrate to v3 all the way up to your current version. The same applies if the user has any other version.

This does mean that it will take a little longer in the migration process, but its a good way of migrating all your users no matter which version they have.

To find this progressive migration code, you need to read a book called Core Data 2nd Edition - Data storage and management for iOS, OS X, and iCloud, Chapter 3 Versioning and Migration sub chapter 3.6 Progressive Data Migration (An Academic Exercise) from pages 54 to 59.

He talks you through the code,and tells you step by step how to write the progressivelyMigrateURL:ofType:toModel:error: method. Once you've written the method with him, he then tells you how to call this method on application startup so that your users can have their stores automatically migrated progressively too.

So you should probably write the method first, then follow my steps up above or you can read through migration policies in the subchapters before.

I practically learned all this in the last 48 hours and have it all up and running now. Able to do lightweight migrations for some versions and then have custom migrations for my other versions all done automatically.

Method 2 - Another solution albeit more cumbersome IMO: You can always have lightweight migration setup bearing in mind that you apparently can achieve even complex situations with lightweight migration. But in the instances where heavy migration is required for a specific version upgrade, you can always do if statements where you can check the current store, and if and ONLY if the current version of the persistent store matches an upgrade where heavy migration is required, you then tell the migration manager to perform a heavy migration and to follow a mapping model with a migration policy for that instance only, and then resume lightweight migration if there are more version upgrades to do to get the persistent store to the most recent model version.

Implementation of Automatic Lightweight Migration for Core Data (iPhone)

This is what I did to make Automatic Lightweight Migration (Source: http://brainwashinc.wordpress.com/2010/01/18/iphone-coredata-automatic-light-migration/)

1. Set the Persistent Store options for automatic migration in the app delegate.

Change your persistentStoreCoordinator creation to this (replace YOURDB):

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}

NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: @"YOURDB.sqlite"]];

// handle db upgrade
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Handle error
}

return persistentStoreCoordinator;
}

2. Version your Data Model and Edit the new file.

Select your xcdatamodel file
Design -> Data Model -> Add Model Version (expand your xcdatamodeld item)
Select the “2″ (or later) file, Design -> Data Model -> Set Current Version (edit this version)

3. Specify the momd resource in app delegate.

Change your managedObjectModel implementation to this (replace YOURDB)

- (NSManagedObjectModel *)managedObjectModel {

if (managedObjectModel != nil) {
return managedObjectModel;
}

NSString *path = [[NSBundle mainBundle] pathForResource:@"YOURDB" ofType:@"momd"];
NSURL *momURL = [NSURL fileURLWithPath:path];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:momURL];

return managedObjectModel;
}

Detecting a Lightweight Core Data Migration

To detect whether a migration is needed, check to see if the persistent store coordinator's managed object model is compatible with the existing store's metadata (adapted from Apple's Is Migration Necessary):

NSError *error = nil;
persistentStoreCoordinator = /* Persistent store coordinator */ ;
NSURL *storeUrl = /* URL for the source store */ ;

// Determine if a migration is needed
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeUrl
error:&error];
NSManagedObjectModel *destinationModel = [persistentStoreCoordinator managedObjectModel];
BOOL pscCompatibile = [destinationModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
NSLog(@"Migration needed? %d", !pscCompatibile);

If pscCompatibile is NO, then a migration will need to occur. To examine the entity changes, compare the NSStoreModelVersionHashes key in the sourceMetadata dictionary to the [destinationModel entities]:

NSSet *sourceEntities = [NSSet setWithArray:[(NSDictionary *)[sourceMetadata objectForKey:@"NSStoreModelVersionHashes"] allKeys]];
NSSet *destinationEntities = [NSSet setWithArray:[(NSDictionary *)[destinationModel entitiesByName] allKeys]];

// Entities that were added
NSMutableSet *addedEntities = [NSMutableSet setWithSet:destinationEntities];
[addedEntities minusSet:sourceEntities];

// Entities that were removed
NSMutableSet *removedEntities = [NSMutableSet setWithSet:sourceEntities];
[removedEntities minusSet:destinationEntities];

NSLog(@"Added entities: %@\nRemoved entities: %@", addedEntities, removedEntities);


Related Topics



Leave a reply



Submit