I've struggled with the same problem before.
I have a method that will attempt to migrate data using Mapping Models which is what you should use if you're going to turn off lightweight migration.
If you aren't going to do a lot of fancy data mapping, xcode will automatically create a mapping model that will work exactly like lightweight migration. All you have to do is create a new "Mapping Model" file each time you add a new version to Core Data. Just go to "File -> New -> New File" and under Core Data there should be a Mapping Model template. Select it and choose the source and destination versions.
I don't have my code openly available on github so I'll just post the migration method here.
- (BOOL)progressivelyMigrateURL:(NSURL*)sourceStoreURL ofType:(NSString*)type toModel:(NSManagedObjectModel*)finalModel
{
NSError *error = nil;
// if store dosen't exist skip migration
NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
if(![NSBundle pathForResource:@"YongoPal" ofType:@"sqlite" inDirectory:documentDir])
{
migrationProgress = 1.0;
[self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];
// remove migration view
[self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
[self.migrationView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:YES];
self.migrationView = nil;
self.migrationProgressLabel = nil;
self.migrationProgressView = nil;
self.migrationSpinner = nil;
return YES;
}
//START:progressivelyMigrateURLHappyCheck
NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:type URL:sourceStoreURL error:&error];
if (!sourceMetadata)
{
return NO;
}
if ([finalModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata])
{
migrationProgress = 1.0;
[self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];
// remove migration view
[self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:[NSNumber numberWithBool:YES] waitUntilDone:YES];
[self.migrationView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:YES];
self.migrationView = nil;
self.migrationProgressLabel = nil;
self.migrationProgressView = nil;
self.migrationSpinner = nil;
error = nil;
return YES;
}
else
{
migrationProgress = 0.0;
[self.migrationView performSelectorOnMainThread:@selector(setHidden:) withObject:NO waitUntilDone:YES];
[self performSelectorOnMainThread:@selector(updateMigrationProgress) withObject:nil waitUntilDone:YES];
}
//END:progressivelyMigrateURLHappyCheck
//START:progressivelyMigrateURLFindModels
//Find the source model
NSManagedObjectModel *sourceModel = [NSManagedObjectModel mergedModelFromBundles:nil forStoreMetadata:sourceMetadata];
if(sourceModel == nil)
{
NSLog(@"%@", [NSString stringWithFormat:@"Failed to find source model\n%@", [sourceMetadata description]]);
return NO;
}
//Find all of the mom and momd files in the Resources directory
NSMutableArray *modelPaths = [NSMutableArray array];
NSArray *momdArray = [[NSBundle mainBundle] pathsForResourcesOfType:@"momd" inDirectory:nil];
for (NSString *momdPath in momdArray)
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *resourceSubpath = [momdPath lastPathComponent];
NSArray *array = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:resourceSubpath];
[modelPaths addObjectsFromArray:array];
[pool drain];
}
NSArray* otherModels = [[NSBundle mainBundle] pathsForResourcesOfType:@"mom" inDirectory:nil];
[modelPaths addObjectsFromArray:otherModels];
if (!modelPaths || ![modelPaths count])
{
//Throw an error if there are no models
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:@"No models found in bundle" forKey:NSLocalizedDescriptionKey];
//Populate the error
error = [NSError errorWithDomain:@"com.yongopal.coredata" code:500 userInfo:dict];
if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
{
NSLog(@"error: %@", error);
}
return NO;
}
//END:progressivelyMigrateURLFindModels
//See if we can find a matching destination model
//START:progressivelyMigrateURLFindMap
NSMappingModel *mappingModel = nil;
NSManagedObjectModel *targetModel = nil;
NSString *modelPath = nil;
for(modelPath in modelPaths)
{
targetModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:[NSURL fileURLWithPath:modelPath]];
mappingModel = [NSMappingModel mappingModelFromBundles:nil forSourceModel:sourceModel destinationModel:targetModel];
//If we found a mapping model then proceed
if(mappingModel)
{
break;
}
else
{
//Release the target model and keep looking
[targetModel release];
targetModel = nil;
}
}
//We have tested every model, if nil here we failed
if (!mappingModel)
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setValue:@"No mapping models found in bundle" forKey:NSLocalizedDescriptionKey];
error = [NSError errorWithDomain:@"com.yongopal.coredata" code:500 userInfo:dict];
if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
{
NSLog(@"error: %@", error);
}
return NO;
}
//END:progressivelyMigrateURLFindMap
//We have a mapping model and a destination model. Time to migrate
//START:progressivelyMigrateURLMigrate
NSMigrationManager *manager = [[NSMigrationManager alloc] initWithSourceModel:sourceModel destinationModel:targetModel];
// reg KVO for migration progress
[manager addObserver:self forKeyPath:@"migrationProgress" options:NSKeyValueObservingOptionNew context:NULL];
NSString *modelName = [[modelPath lastPathComponent] stringByDeletingPathExtension];
NSString *storeExtension = [[sourceStoreURL path] pathExtension];
NSString *storePath = [[sourceStoreURL path] stringByDeletingPathExtension];
//Build a path to write the new store
storePath = [NSString stringWithFormat:@"%@.%@.%@", storePath, modelName, storeExtension];
NSURL *destinationStoreURL = [NSURL fileURLWithPath:storePath];
if (![manager migrateStoreFromURL:sourceStoreURL type:type options:nil withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:type destinationOptions:nil error:&error])
{
if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
{
NSLog(@"error: %@", error);
}
[targetModel release];
[manager removeObserver:self forKeyPath:@"migrationProgress"];
[manager release];
return NO;
}
[targetModel release];
[manager removeObserver:self forKeyPath:@"migrationProgress"];
[manager release];
//END:progressivelyMigrateURLMigrate
//Migration was successful, move the files around to preserve the source
//START:progressivelyMigrateURLMoveAndRecurse
NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString];
guid = [guid stringByAppendingPathExtension:modelName];
guid = [guid stringByAppendingPathExtension:storeExtension];
NSString *appSupportPath = [storePath stringByDeletingLastPathComponent];
NSString *backupPath = [appSupportPath stringByAppendingPathComponent:guid];
NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager moveItemAtPath:[sourceStoreURL path] toPath:backupPath error:&error])
{
if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
{
NSLog(@"error: %@", error);
}
//Failed to copy the file
return NO;
}
//Move the destination to the source path
if (![fileManager moveItemAtPath:storePath toPath:[sourceStoreURL path] error:&error])
{
if([[self.prefs valueForKey:@"debugMode"] isEqualToString:@"Y"])
{
NSLog(@"error: %@", error);
}
//Try to back out the source move first, no point in checking it for errors
[fileManager moveItemAtPath:backupPath toPath:[sourceStoreURL path] error:nil];
return NO;
}
//We may not be at the "current" model yet, so recurse
return [self progressivelyMigrateURL:sourceStoreURL ofType:type toModel:finalModel];
//END:progressivelyMigrateURLMoveAndRecurse
}
This is an edited version of a method I got from some Core Data book I can't remember the title of. I wish I could give credit to the author. :S
Beware, I have some code in here that you should remove in your implementation. It's mostly stuff I use to update the view on the progress of the migration.
You can use this method like so:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"YongoPal.sqlite"];
// perform core data migrations if necessary
if(![self progressivelyMigrateURL:storeURL ofType:NSSQLiteStoreType toModel:self.managedObjectModel])
{
// reset the persistent store on fail
NSString *documentDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtPath:[NSBundle pathForResource:@"YongoPal" ofType:@"sqlite" inDirectory:documentDir] error:&error];
}
else
{
NSLog(@"migration succeeded!");
}
Remember to remove the lightweight migration option before you use this.