Ho capito cosa suggerisce Apple nella loro documentazione . In realtà è molto facile, ma c'è ancora molta strada da fare prima che sia ovvio. Illustrerò la spiegazione con un esempio. La situazione iniziale è questa:
Data Model versione 1
È il modello che ottieni quando crei un progetto con il modello "app basata sulla navigazione con archiviazione dei dati di base". L'ho compilato e ho fatto un po 'di duro lavoro con l'aiuto di un ciclo for per creare circa 2k voci tutte con valori diversi. Ecco 2.000 eventi con un valore NSDate.
Ora aggiungiamo una seconda versione del modello di dati, che assomiglia a questo:
Data Model versione 2
La differenza è: l'entità Evento è scomparsa e ne abbiamo due nuove. Uno che memorizza un timestamp come a double
e il secondo che dovrebbe memorizzare una data come NSString
.
L'obiettivo è trasferire tutta la versione 1 eventi della alle due nuove entità e convertire i valori durante la migrazione. Ciò si traduce in due volte i valori ciascuno come un tipo diverso in un'entità separata.
Per migrare, scegliamo la migrazione manualmente e questo lo facciamo con i modelli di mappatura. Questa è anche la prima parte della risposta alla tua domanda. Faremo la migrazione in due passaggi, perché la migrazione di 2k voci richiede molto tempo e ci piace mantenere basso il footprint di memoria.
Potresti anche andare avanti e dividere ulteriormente questi modelli di mappatura per migrare solo gli intervalli delle entità. Diciamo che abbiamo un milione di record, questo potrebbe mandare in crash l'intero processo. È possibile restringere le entità recuperate con un predicato di filtro .
Torniamo ai nostri due modelli di mappatura.
Creiamo il primo modello di mappatura in questo modo:
1. Nuovo file -> Risorsa -> Modello di mappatura
2. Scegli un nome, ho scelto StepOne
3. Impostare il modello di dati di origine e di destinazione
Fase uno del modello di mappatura
La migrazione a più passaggi non necessita di criteri di migrazione delle entità personalizzate, tuttavia lo faremo per ottenere un po 'più di dettagli per questo esempio. Quindi aggiungiamo una policy personalizzata all'entità. Questa è sempre una sottoclasse di NSEntityMigrationPolicy
.
Questa classe di criteri implementa alcuni metodi per far sì che la nostra migrazione avvenga. Tuttavia è semplice in questo caso in modo dovremo implementare un solo metodo: createDestinationInstancesForSourceInstance:entityMapping:manager:error:
.
L'implementazione sarà simile a questa:
StepOneEntityMigrationPolicy.m
#import "StepOneEntityMigrationPolicy.h"
@implementation StepOneEntityMigrationPolicy
- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject *)sInstance
entityMapping:(NSEntityMapping *)mapping
manager:(NSMigrationManager *)manager
error:(NSError **)error
{
NSManagedObject *newObject =
[NSEntityDescription insertNewObjectForEntityForName:[mapping destinationEntityName]
inManagedObjectContext:[manager destinationContext]];
NSDate *date = [sInstance valueForKey:@"timeStamp"];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[newObject setValue:[dateFormatter stringFromDate:date] forKey:@"printedDate"];
[dateFormatter release];
[manager associateSourceInstance:sInstance withDestinationInstance:newObject forEntityMapping:mapping];
return YES;
}
Passaggio finale: la migrazione stessa
Salterò la parte per impostare il secondo modello di mappatura che è quasi identico, solo un timeIntervalSince1970 utilizzato per convertire NSDate in un doppio.
Infine dobbiamo attivare la migrazione. Salterò il codice boilerplate per ora. Se ne hai bisogno, posterò qui. Può essere trovato su Personalizzazione del processo di migrazione , è solo una fusione dei primi due esempi di codice. La terza e ultima parte verrà modificata come segue: Invece di usare il metodo class della NSMappingModel
classe mappingModelFromBundles:forSourceModel:destinationModel:
useremo ilinitWithContentsOfURL:
perché il metodo class restituirà solo uno, forse il primo, modello di mappatura trovato nel bundle.
Ora abbiamo i due modelli di mappatura che possono essere utilizzati in ogni passaggio del ciclo e inviare il metodo di migrazione al gestore della migrazione. Questo è tutto.
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:&error2];
[mappingModel release];
}
Appunti
Un modello di mappatura termina nel cdm
bundle.
L'archivio di destinazione deve essere fornito e non dovrebbe essere l'archivio di origine. Dopo la corretta migrazione, è possibile eliminare il vecchio e rinominare quello nuovo.
Ho apportato alcune modifiche al modello dati dopo la creazione dei modelli di mappatura, questo ha comportato alcuni errori di compatibilità, che ho potuto risolvere solo ricreando i modelli di mappatura.