Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>I've figured out what Apple hints in their <a href="https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmCustomizing.html#//apple_ref/doc/uid/TP40004399-CH8-SW1" rel="noreferrer">documentation</a>. It's actually very easy but a long way to go before it's obvious. I'll illustrate the explanation with an example. The initial situation is this:</p> <h2>Data Model Version 1</h2> <p><img src="https://i.stack.imgur.com/69Rex.png" alt="enter image description here"> <img src="https://i.stack.imgur.com/T6i3n.png" alt="enter image description here"></p> <p>It's the model you get when you create a project with the "navigation based app with core data storage" template. I compiled it and did some hard hitting with some help of a for loop to create around 2k entries all with some different values. There we go 2.000 events with an NSDate value.</p> <p>Now we add a second version of the data model, which looks like this:</p> <p><img src="https://i.stack.imgur.com/T14rf.png" alt="enter image description here"></p> <h2>Data Model Version 2</h2> <p>The difference is: The Event entity is gone, and we've got two new ones. One which stores a timestamp as a <code>double</code> and the second one which should store a date as <code>NSString</code>.</p> <p>The goal is to transfer all <strong>Version 1</strong> Events to the two new entities and convert the values along the migration. This results in twice the values each as a different type in a separate entitiy.</p> <p>To migrate, we choose migration by hand and this we do with mapping models. This is also the first part of the answer to your question. We will do the migration in two steps, because it's taking long to migrate 2k entries and we like to keep the memory footprint low. </p> <p>You could even go ahead and split these mapping models further to migrate only ranges of the entities. Say we got one million records, this may crash the whole process. It's possible to narrow the fetched entities down with a <a href="http://developer.apple.com/library/ios/#documentation/DeveloperTools/Conceptual/XcodeMappingTool/Articles/xmmModelPane.html#//apple_ref/doc/uid/TP40006865-SW1" rel="noreferrer">Filter predicate</a>.</p> <h2>Back to our two mapping models.</h2> <p>We create the first mapping model like this:</p> <p><em>1. New File -> Resource -> Mapping Model</em> <img src="https://i.stack.imgur.com/4eV4U.png" alt="enter image description here"></p> <p><em>2. Choose a name, I chose StepOne</em></p> <p><em>3. Set source and destination data model</em></p> <p><img src="https://i.stack.imgur.com/hWddI.png" alt="enter image description here"></p> <h2>Mapping Model Step One</h2> <p><img src="https://i.stack.imgur.com/eKXB3.png" alt="enter image description here"></p> <p><img src="https://i.stack.imgur.com/uM34V.png" alt="enter image description here"></p> <p><img src="https://i.stack.imgur.com/GDVnC.png" alt="enter image description here"></p> <p>The multi pass migration doesn't need custom entity migration policies, however we will do it to get a bit more detail for this example. So we add a custom policy to the entity. This is always a subclass of <a href="http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/NSEntityMigrationPolicy_class/NSEntityMigrationPolicy.html" rel="noreferrer"><code>NSEntityMigrationPolicy</code></a>. </p> <p><img src="https://i.stack.imgur.com/6fZmi.png" alt="enter image description here"></p> <p>This policy class implements some methods to make our migration happen. However it's simple in this case so we will have to implement only one method: <a href="http://developer.apple.com/library/mac/documentation/Cocoa/Reference/NSEntityMigrationPolicy_class/NSEntityMigrationPolicy.html#//apple_ref/doc/uid/TP40004612-CH1-SW1" rel="noreferrer"><code>createDestinationInstancesForSourceInstance:entityMapping:manager:error:</code></a>. </p> <p>The implementation will look like this:</p> <h3>StepOneEntityMigrationPolicy.m</h3> <pre><code>#import "StepOneEntityMigrationPolicy.h" @implementation StepOneEntityMigrationPolicy - (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance entityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError **)error { // Create a new object for the model context NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName] inManagedObjectContext:[manager destinationContext]]; // do our transfer of nsdate to nsstring NSDate *date = [sInstance valueForKey:@"timeStamp"]; NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; // set the value for our new object [newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"]; [dateFormatter release]; // do the coupling of old and new [manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping]; return YES; } </code></pre> <h2>Final step: the migration itself</h2> <p>I'll skip the part for setting up the second mapping model which is almost identical, just a timeIntervalSince1970 used to convert the NSDate to a double. </p> <p>Finally we need to trigger the migration. I'll skip the boilerplate code for now. If you need it, I'll post here. It can be found at <a href="https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/vmCustomizing.html#//apple_ref/doc/uid/TP40004399-CH8-SW1" rel="noreferrer">Customizing the Migration Process</a> it's just a merge of the first two code examples. The third and last part will be modified as follows: Instead of using the class method of the <a href="http://developer.apple.com/library/ios/#documentation/cocoa/Reference/NSMappingModel_class/NSMappingModel.html" rel="noreferrer"><code>NSMappingModel</code></a> class <a href="http://developer.apple.com/library/ios/documentation/cocoa/Reference/NSMappingModel_class/NSMappingModel.html#//apple_ref/doc/uid/TP40004609-CH1-SW5" rel="noreferrer"><code>mappingModelFromBundles:forSourceModel:destinationModel:</code></a> we will use the <a href="http://developer.apple.com/library/ios/documentation/cocoa/Reference/NSMappingModel_class/NSMappingModel.html#//apple_ref/doc/uid/TP40004609-CH1-SW3" rel="noreferrer"><code>initWithContentsOfURL:</code></a> because the class method will return only one, maybe the first, found mapping model in the bundle. </p> <p>Now we've got the two mapping models which can be used in every pass of the lopp and send the migrate method to the migration manager. That's it.</p> <pre><code>NSArray *mappingModelNames = [NSArray arrayWithObjects:@"StepOne", @"StepTwo", nil]; NSDictionary *sourceStoreOptions = nil; NSURL *destinationStoreURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"CoreDataMigrationNew.sqlite"]; NSString *destinationStoreType = NSSQLiteStoreType; NSDictionary *destinationStoreOptions = nil; for (NSString *mappingModelName in mappingModelNames) { NSURL *fileURL = [[NSBundle mainBundle] URLForResource:mappingModelName withExtension:@"cdm"]; NSMappingModel *mappingModel = [[NSMappingModel alloc] initWithContentsOfURL:fileURL]; BOOL ok = [migrationManager migrateStoreFromURL:sourceStoreURL type:sourceStoreType options:sourceStoreOptions withMappingModel:mappingModel toDestinationURL:destinationStoreURL destinationType:destinationStoreType destinationOptions:destinationStoreOptions error:&amp;error2]; [mappingModel release]; } </code></pre> <p><strong>Notes</strong></p> <ul> <li><p>A mapping model ends in <code>cdm</code> in the bundle.</p></li> <li><p>The destination store has to be provided and should not be the source store. You can after successful migration delete the old and rename the new one.</p></li> <li><p>I did some changes to the data model after the creation of the mapping models, this resulted in some compatibility errors, which I could only solve with recreating the mapping models.</p></li> </ul>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload