- Changing a Managed Object Model
- Adding a Model Version
- Lightweight Migration
- Default Migration
- Migration Manager
- Summary
- Exercises
Lightweight Migration
Whenever a new model is set as the current version, existing persistent stores must be migrated to use them. This is because the persistent store coordinator will try to use the latest model to open the existing store, which will fail if the store was created using a previous version of the model. The process of store migration can be handled automatically by passing the following options to a persistent store coordinator in an NSDictionary as a store is added to it:
- When NSMigratePersistentStoresAutomaticallyOption is YES and passed to a persistent store coordinator, Core Data will automatically attempt to migrate lower versioned (and hence incompatible) persistent stores to the latest model.
- When NSInferMappingModelAutomaticallyOption is YES and passed to a persistent store coordinator, Core Data will automatically attempt to infer a best guess at what attributes from the source model entities should end up as attributes in the destination model entities.
Using those persistent store coordinator options together is called lightweight migration and is demonstrated in bold in Listing 3.1. These options are set in an updated loadStore method of CoreDataHelper.m. Note that if you’re using iCloud, this is your only choice for migration.
Listing 3.1 CoreDataHelper.m: loadStore
if (debug==1) { NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd)); } if (_store) {return;} // Don't load store if it’s already loaded NSDictionary *options = @{ NSMigratePersistentStoresAutomaticallyOption:@YES ,NSInferMappingModelAutomaticallyOption:@YES ,NSSQLitePragmasOption: @{@"journal_mode": @"DELETE"} }; NSError *error = nil; _store = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self storeURL] options:options error:&error]; if (!_store) {NSLog(@"Failed to add store. Error: %@", error);abort();} else {NSLog(@"Successfully added store: %@", _store);}
Update Grocery Dude as follows to enable lightweight migration:
- Replace all existing code in the loadStore method of CoreDataHelper.m with the code from Listing 3.1. For reference, the bold code shows the new changes.
- Re-run the application, which should not crash.
From now on, any time you set a new model as the current version and lightweight migration is enabled, the migration should occur seamlessly.
Before other migration types can be demonstrated, some test data needs to be generated. Listing 3.2 contains code that generates managed objects based on the Measurement entity.
Listing 3.2 AppDelegate.m: demo (Inserting Test Measurement Data)
- (void)demo { if (debug==1) { NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd)); } for (int i = 1; i < 50000; i++) { Measurement *newMeasurement = [NSEntityDescription insertNewObjectForEntityForName:@"Measurement" inManagedObjectContext:_coreDataHelper.context]; newMeasurement.abc = [NSString stringWithFormat:@"-->> LOTS OF TEST DATA x%i",i]; NSLog(@"Inserted %@",newMeasurement.abc); } [_coreDataHelper saveContext]; }
Update Grocery Dude as follows to generate test data:
- Create an NSManagedObject subclass of the Measurement entity. As discussed in Chapter 2, this is achieved by first selecting the entity and then clicking Editor > Create NSManagedObject Subclass... and following the prompts. When it comes time to save the class file, don’t forget to tick the Grocery Dude target.
- Add #import "Measurement.h" to the top of AppDelegate.m.
- Replace the demo method in AppDelegate.m with the code from Listing 3.2.
- Run the application once. This will insert quite a lot of test data into the persistent store, which you can monitor by examining the console log. This may take a little while, depending on the speed of your machine. Please be patient as these objects are inserted. It’s important to have a fair amount of data in the persistent store to demonstrate the speed of migrations later.
The test data should now be in the persistent store, so there’s no need to reinsert it each time the application is launched. Note that the Items table view will still remain blank because it has not yet been configured to display anything.
The next step is to reconfigure the demo method to show some of what’s in the persistent store. The code shown in Listing 3.3 fetches a small sample of Measurement data. Notice that a new option is included that limits fetched results to 50. This is great for limiting how many results are fetched from large data sets, and even more powerful when mixed with sorting to generate a Top-50, for example.
Listing 3.3 AppDelegate.m: demo (Fetching Test Measurement Data)
- (void)demo { if (debug==1) { NSLog(@"Running %@ '%@'", self.class, NSStringFromSelector(_cmd)); } NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Measurement"]; [request setFetchLimit:50]; NSError *error = nil; NSArray *fetchedObjects = [_coreDataHelper.context executeFetchRequest:request error:&error]; if (error) {NSLog(@"%@", error);} else { for (Measurement *measurement in fetchedObjects) { NSLog(@"Fetched Object = %@", measurement.abc); } } }
Update Grocery Dude as follows to prevent duplicate test data from being inserted:
- Replace the demo method in AppDelegate.m with the code from Listing 3.3.
- Run the application.
- Examine the contents of the Grocery-Dude.sqlite file using SQLite Database Browser, as explained previously in Chapter 2. Figure 3.4 shows the expected results.
Figure 3.4 Test data ready for the next parts of this chapter
Ensure you close SQLite Database Browser before continuing.