This post will show how to initiate default migration using the mapping model and custom migration policy code from the last posting.
Apple's Core Data Model Versioning and Data Migration Programming Guide describes three scenarios for data migration:
1. App does not support model versioning.
2. App supports model versioning and migrates using default or lightweight migration.
3. App supports model versioning and implements custom version skew detection and migration bootstrapping.
In my case, the second scenario is what will define the migration initiation. (If you want to learn more about 1 and 3 read the linked Core Data migration guide.) Lightweight migration is the case where Core Data attempts to infer the mapping model and perform automatic migration. Since the change of type from double to decimal is not something Core Data can infer, I needed to use default migration.
Default migration entails providing Core Data a mapping model and any necessary NSEntityMigrationPolicy subclasses - which I did as part of the previous two posts. The last bit involves setting the version of the data model document, generate updated NSManagedObject subclasses for the new entities, and to pass in an options dictionary to addPersistentStoreWithType: configuration: options: URL: options: error: method call when creating the persistent store coordinator. The first two steps are trivial so we'll jump right into the last part.
As it turns out, I had already done a previous migration; it was lightweight and Core Data was able to infer the mapping model. Here is the code used for the persistent store coordinator property getter:
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator; - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { // 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. if (_persistentStoreCoordinator != nil) { return _persistentStoreCoordinator; } NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Spendu.sqlite"]; NSError *error = nil; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; // add an options dictionary to automatically do lightweight migration if // the persistent store coordinator can infer the mapping itself NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil]; if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { SDUTRACE_ERR(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return _persistentStoreCoordinator; }
The options dictionary is what's important here. Out of the box a Core Data Xcode project simply passes in nil for the options in the addPersistentStoreWithType: call. In order to enable the default migration and not lightweight, I had to remove the NSInferMappingModelAutomaticallyOption key from the options dictionary.
Before initiating the moment of truth, it is important to find a way to test the migration process. I did not implement any unit tests to exercise the policies - though that is an ideal situation. Instead I identified a Git commit version that still used the previous model version that I could always check out and create test data with. Then I would check out the master and run the simulator. This way I could always initiate migration after creating test data.
Moreover, I created breakpoints in Xcode in places of interest within my implementation of createDestinationInstancesForSourceInstances: override. Xcode allows editing breakpoints to execute lldb statements whenever they are hit. This way I could analyze the migration procedure and make sure everything was going ok:
Notice how the source instance's budget is stored (double type) versus the destination entity's budget (decimal type). Also the rounding is perfect.
Assuming my migration goes well all that I need to do is convert the user-facing UI to handle NSDecimalNumber and update all my unit tests to do the same. (Which happens to be a shit ton of work. :\)
No comments:
Post a Comment