Customizing the Migration Process

You only customize the migration process if you want to initiate migration yourself. You might do this to, for example, to search for models in locations other than the application’s main bundle, or to deal with large data sets by performing the migration in several passes using different mapping models (see Multiple Passes—Dealing With Large Datasets).

Is Migration Necessary

Before you initiate a migration process, you should first determine whether it is necessary. You can check with NSManagedObjectModel’s isConfiguration:compatibleWithStoreMetadata: as illustrated in Listing 7-1.

Listing 7-1  Checking whether migration is necessary

NSPersistentStoreCoordinator *psc = /* get a coordinator */ ;
NSString *sourceStoreType = /* type for the source store, or nil if not known */ ;
NSURL *sourceStoreURL = /* URL for the source store */ ;
NSError *error = nil;
 
NSDictionary *sourceMetadata =
    [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:sourceStoreType
                                  URL:sourceStoreURL
                                  error:&error];
 
if (sourceMetadata == nil) {
    // deal with error
}
 
NSString *configuration = /* name of configuration, or nil */ ;
NSManagedObjectModel *destinationModel = [psc managedObjectModel];
BOOL pscCompatibile = [destinationModel
            isConfiguration:configuration
            compatibleWithStoreMetadata:sourceMetadata];
 
if (pscCompatibile) {
    // no need to migrate
}

Initializing a Migration Manager

You initialize a migration manager using initWithSourceModel:destinationModel:; you therefore first need to find the appropriate model for the store. You get the model for the store using NSManagedObjectModel’s mergedModelFromBundles:forStoreMetadata:. If this returns a suitable model, you can create the migration manager as illustrated in Listing 7-2 (this code fragment continues from Listing 7-1).

Listing 7-2  Initializing a Migration Manager

NSArray *bundlesForSourceModel = /* an array of bundles, or nil for the main bundle */ ;
NSManagedObjectModel *sourceModel =
    [NSManagedObjectModel mergedModelFromBundles:bundlesForSourceModel
                            forStoreMetadata:sourceMetadata];
 
if (sourceModel == nil) {
    // deal with error
}
 
MyMigrationManager *migrationManager =
    [[MyMigrationManager alloc]
            initWithSourceModel:sourceModel
            destinationModel:destinationModel];

Performing a Migration

You migrate a store using NSMigrationManager’s migrateStoreFromURL:type:options:withMappingModel:toDestinationURL:destinationType:destinationOptions:error:. To use this method you need to marshal a number of parameters; most are straightforward, the only one that requires some work is the discovery of the appropriate mapping model (which you can retrieve using NSMappingModel’s mappingModelFromBundles:forSourceModel:destinationModel: method). This is illustrated in Listing 7-3 (a continuation of the example shown in Listing 7-2).

Listing 7-3  Performing a Migration

NSArray *bundlesForMappingModel = /* an array of bundles, or nil for the main bundle */ ;
NSError *error = nil;
 
NSMappingModel *mappingModel =
    [NSMappingModel
            mappingModelFromBundles:bundlesForMappingModel
            forSourceModel:sourceModel
            destinationModel:destinationModel];
 
if (mappingModel == nil) {
        // deal with the error
}
 
NSDictionary *sourceStoreOptions = /* options for the source store */ ;
NSURL *destinationStoreURL = /* URL for the destination store */ ;
NSString *destinationStoreType = /* type for the destination store */ ;
NSDictionary *destinationStoreOptions = /* options for the destination store */ ;
 
BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL
                  type:sourceStoreType
                  options:sourceStoreOptions
                  withMappingModel:mappingModel
                  toDestinationURL:destinationStoreURL
                  destinationType:destinationStoreType
                  destinationOptions:destinationStoreOptions
                  error:&error];

Multiple Passes—Dealing With Large Datasets

The basic approach shown above is to have the migration manager take two models, and then iterate over the steps (mappings) provided in a mapping model to move the data from one side to the next. Because Core Data performs a "three stage" migration—where it creates all of the data first, and then relates the data in a second stage—it must maintain “association tables" (which tell it which object in the destination store is the migrated version of which object in the source store, and vice-versa). Further, because it doesn't have a means to flush the contexts it is working with, it means you'll accumulate many objects in the migration manager as the migration progresses.

In order to address this, the mapping model is given as a parameter of the migrateStoreFromURL:type:options:withMappingModel:toDestinationURL:destinationType:destinationOptions:error: call itself. What this means is that if you can segregate parts of your graph (as far as mappings are concerned) and create them in separate mapping models, you could do the following:

  1. Get the source and destination data models

  2. Create a migration manager with them

  3. Find all of your mapping models, and put them into an array (in some defined order, if necessary)

  4. Loop through the array, and call migrateStoreFromURL:type:options:withMappingModel:toDestinationURL:destinationType:destinationOptions:error: with each of the mappings

This allows you to migrate "chunks" of data at a time, while not pulling in all of the data at once.

From a "tracking/showing progress” point of view, that basically just creates another layer to work from, so you'd be able to determine percentage complete based on number of mapping models to iterate through (and then further on the number of entity mappings in a model you've already gone through).