| Core Data Migration - Standard Migration Part 2: Migration Boogaloo |
|
| Written by Mike Post |
| Friday, 13 January 2012 16:11 |
|
UPDATE: Some people have said they've had mapping model problems. I personally didn't experience this, but the project is now uploaded to Github so you can check it out in it's entirety or modify it: ********************************************************************************************************************************************************************* Please excuse the title, it's a personal rule of mine to append "boogaloo" to any subject that ends in the number 2. "An in-depth discussion about standard migration is beyond the scope of this tutorial/book" Remember this? This was the opening phrase in Part 1 of my Core Data Standard Migration tutorial, in which I:
I've repeated the above quote in the introduction of Part 2 because it's worth reminding you that this IS NOT what this tutorial is about! We're going to dig in deep and tackle the hard problems of Standard Migration, not Lightweight Migration (which is extensively covered everywhere else, including the moon). The only reason there is even a Part 2 is because Part 1 was getting ridiculously long, and you would probably already be bored by the time the practical element was about to begin (MTV generation, low attention spans, etc). I'm going to provide you with a logical style of learning the process. Firstly explaining what we need to do, then outlining the practical steps to achieve this. I'm also going to accompany the explanations with references to the Apple docs where I can, for verification if you need it explained in the more mechanical Apple-like manner, and so you can confirm that i'm not pulling this information from a mythical repository in the sky. NOTE: The accompanying images and explanations of this tutorial are made with Xcode 4.2 in mind. You'll have to do things a little differently if you have any version of Xcode less than 4.0. Sorry, but I don't deal with backwards compatibility and neither should you, it's the Apple way.
1.
STEPS:
2.
STEPS:
3.
STEPS:
4.
STEPS:
5.
STEPS:
6.
7.
STEPS:
8.
STEPS:
9.
STEPS:
10.
STEPS:
That's it! Verify if the store data was compatible (it should be NO), and if the migration was successful (it should be YES) in the logs to put your mind at ease. Please ask any questions below if I've taken this newfound knowledge for granted and have left out any essential steps in this tutorial, or if I haven't explained anything well enough. Below is my code for the whole RecipesAppDelegate implementation file. Just to recap, the main methods we've modified or added here are persistentStoreCoordinator, checkForMigration, and performMigrationWithSourceMetadata.
http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreDataVersioning/Introduction/Introduction.html These are 2 very good reads but are confined to OS X, not iOS: http://www.timisted.net/blog/archive/core-data-migration/ http://pragprog.com/book/mzcd/core-data
RecipesAppDelegate.m: #import "RecipesAppDelegate.h"
#import "RecipeListTableViewController.h"
#import "UnitConverterTableViewController.h"
@implementation RecipesAppDelegate
@synthesize window;
@synthesize tabBarController;
@synthesize recipeListController;
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
recipeListController.managedObjectContext = self.managedObjectContext;
[window addSubview:tabBarController.view];
[window makeKeyAndVisible];
}
/**
* applicationWillTerminate: saves changes in the application's managed object context,
* before the application terminates.
*/
- (void)applicationWillTerminate:(UIApplication *)application {
NSError *error;
if (managedObjectContext != nil) {
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
}
#pragma mark -
#pragma mark Core Data stack
/**
* Returns the managed object context for the application.
* If the context doesn't already exist,
* it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext != nil) {
return managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext = [NSManagedObjectContext new];
[managedObjectContext setPersistentStoreCoordinator: coordinator];
}
return managedObjectContext;
}
/**
* Returns the managed object model for the application.
* If the model doesn't already exist, it is created by merging all the models found in the app bundle.
*/
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel != nil) {
return managedObjectModel;
}
// managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"Recipes" ofType:@"momd"];
NSURL *modelURL = [NSURL fileURLWithPath:modelPath];
managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return managedObjectModel;
}
/**
* Returns the persistent store coordinator for the application.
* If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory]
stringByAppendingPathComponent:@"Recipes.sqlite"];
[storePath retain];
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"Recipes" ofType:@"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
//Check to see what version of the current model we're in. If it's >= 2.0,
//then and ONLY then check if migration has been performed...
NSSet *versionIdentifiers = [[self managedObjectModel] versionIdentifiers];
NSLog(@"Which Current Version is our .xcdatamodeld file set to? %@", versionIdentifiers);
if ([versionIdentifiers containsObject:@"2.0"])
{
BOOL hasMigrated = [self checkForMigration];
if (hasMigrated==YES) {
[storePath release];
storePath = nil;
storePath = [[self applicationDocumentsDirectory]
stringByAppendingPathComponent:@"Recipes2.sqlite"];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSError *error;
NSDictionary *pscOptions = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:NO], NSInferMappingModelAutomaticallyOption,
nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeUrl
options:pscOptions
error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator;
}
- (BOOL)checkForMigration
{
BOOL migrationSuccess = NO;
NSString *storeSourcePath = [[self applicationDocumentsDirectory]
stringByAppendingPathComponent:@"Recipes2.sqlite"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:storeSourcePath]) {
//Version 2 SQL has not been created yet, so the source is still version 1...
storeSourcePath = [[self applicationDocumentsDirectory]
stringByAppendingPathComponent:@"Recipes.sqlite"];
}
NSURL *storeSourceUrl = [NSURL fileURLWithPath: storeSourcePath];
NSError *error = nil;
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator
metadataForPersistentStoreOfType:NSSQLiteStoreType
URL:storeSourceUrl
error:&error];
if (sourceMetadata) {
NSString *configuration = nil;
NSManagedObjectModel *destinationModel = [self.persistentStoreCoordinator managedObjectModel];
//Our Source 1 is going to be incompatible with the Version 2 Model, our Source 2 won't be...
BOOL pscCompatible = [destinationModel isConfiguration:configuration compatibleWithStoreMetadata:sourceMetadata];
NSLog(@"Is the STORE data COMPATIBLE? %@", (pscCompatible==YES) ?@"YES" :@"NO");
if (pscCompatible == NO) {
migrationSuccess = [self performMigrationWithSourceMetadata:sourceMetadata toDestinationModel:destinationModel];
}
}
else {
NSLog(@"checkForMigration FAIL - No Source Metadata! \nERROR: %@", [error localizedDescription]);
}
return migrationSuccess;
}
- (BOOL)performMigrationWithSourceMetadata :(NSDictionary *)sourceMetadata
toDestinationModel:(NSManagedObjectModel *)destinationModel
{
BOOL migrationSuccess = NO;
//Initialise a Migration Manager...
NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil
forStoreMetadata:sourceMetadata];
//Perform the migration...
if (sourceModel) {
NSMigrationManager *standardMigrationManager = [[NSMigrationManager alloc]
initWithSourceModel:sourceModel
destinationModel:destinationModel];
//Retrieve the appropriate mapping model...
NSMappingModel *mappingModel = [NSMappingModel mappingModelFromBundles:nil
forSourceModel:sourceModel
destinationModel:destinationModel];
if (mappingModel) {
NSError *error = nil;
NSString *storeSourcePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Recipes.sqlite"];
NSURL *storeSourceUrl = [NSURL fileURLWithPath: storeSourcePath];
NSString *storeDestPath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"Recipes2.sqlite"];
NSURL *storeDestUrl = [NSURL fileURLWithPath:storeDestPath];
//Pass nil here because we don't want to use any of these options:
//NSIgnorePersistentStoreVersioningOption, NSMigratePersistentStoresAutomaticallyOption, or NSInferMappingModelAutomaticallyOption
NSDictionary *sourceStoreOptions = nil;
NSDictionary *destinationStoreOptions = nil;
migrationSuccess = [standardMigrationManager migrateStoreFromURL:storeSourceUrl
type:NSSQLiteStoreType
options:sourceStoreOptions
withMappingModel:mappingModel
toDestinationURL:storeDestUrl
destinationType:NSSQLiteStoreType
destinationOptions:destinationStoreOptions
error:&error];
NSLog(@"MIGRATION SUCCESSFUL? %@", (migrationSuccess==YES)?@"YES":@"NO");
}
}
else {
//TODO: Error to user...
NSLog(@"checkForMigration FAIL - No Mapping Model found!");
abort();
}
return migrationSuccess;
}//END
#pragma mark -
#pragma mark Application's documents directory
/**
* Returns the path to the application's documents directory.
*/
- (NSString *)applicationDocumentsDirectory {
return [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
}
#pragma mark -
#pragma mark Memory management
- (void)dealloc {
[managedObjectContext release];
[managedObjectModel release];
[persistentStoreCoordinator release];
[recipeListController release];
[tabBarController release];
[window release];
[super dealloc];
}
@end
|







