J'ai compris ce qu'Apple laisse entendre dans sa documentation . C'est en fait très facile mais un long chemin à parcourir avant que ce soit évident. Je vais illustrer l'explication avec un exemple. La situation initiale est la suivante:
Modèle de données version 1
C'est le modèle que vous obtenez lorsque vous créez un projet avec le modèle "Application basée sur la navigation avec stockage de données de base". Je l'ai compilé et j'ai fait quelques coups durs avec l'aide d'une boucle for pour créer environ 2k entrées, toutes avec des valeurs différentes. Là, nous allons 2.000 événements avec une valeur NSDate.
Maintenant, nous ajoutons une deuxième version du modèle de données, qui ressemble à ceci:
Modèle de données version 2
La différence est la suivante: l'entité Événement a disparu et nous en avons deux nouvelles. Un qui stocke un horodatage en tant que a double
et le second qui doit stocker une date en tant que NSString
.
L'objectif est de transférer tous les événements de la version 1 vers les deux nouvelles entités et de convertir les valeurs tout au long de la migration. Il en résulte deux fois les valeurs, chacune sous la forme d'un type différent dans une entité distincte.
Pour migrer, nous choisissons la migration à la main et nous le faisons avec des modèles de cartographie. C'est aussi la première partie de la réponse à votre question. Nous allons faire la migration en deux étapes, car la migration de 2k entrées prend du temps et nous aimons garder l'empreinte mémoire faible.
Vous pouvez même aller de l'avant et fractionner ces modèles de mappage pour ne migrer que des plages d'entités. Disons que nous avons un million d'enregistrements, cela risque de faire planter tout le processus. Il est possible de réduire les entités récupérées avec un prédicat Filter .
Revenons à nos deux modèles de cartographie.
Nous créons le premier modèle de mappage comme ceci:
1. Nouveau fichier -> Ressource -> Modèle de mappage
2. Choisissez un nom, j'ai choisi StepOne
3. Définir le modèle de données source et destination
Modèle de cartographie, première étape
La migration multi-passes n'a pas besoin de politiques de migration d'entités personnalisées, mais nous le ferons pour obtenir un peu plus de détails sur cet exemple. Nous ajoutons donc une politique personnalisée à l'entité. Il s'agit toujours d'une sous-classe de NSEntityMigrationPolicy
.
Cette classe de stratégie implémente certaines méthodes pour effectuer notre migration. Cependant , il est simple dans ce cas pour que nous devrons mettre en œuvre qu'une seule méthode: createDestinationInstancesForSourceInstance:entityMapping:manager:error:
.
La mise en œuvre ressemblera à ceci:
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;
}
Dernière étape: la migration elle-même
Je vais sauter la partie pour mettre en place le deuxième modèle de mappage qui est presque identique, juste un timeIntervalSince1970 utilisé pour convertir le NSDate en double.
Enfin, nous devons déclencher la migration. Je vais sauter le code standard pour le moment. Si vous en avez besoin, je posterai ici. Il peut être trouvé dans Personnalisation du processus de migration, il s'agit simplement d'une fusion des deux premiers exemples de code. La troisième et dernière partie sera modifiée comme suit: Au lieu d'utiliser la méthode de classe de la NSMappingModel
classe, mappingModelFromBundles:forSourceModel:destinationModel:
nous utiliserons le initWithContentsOfURL:
car la méthode de classe ne retournera qu'un seul, peut-être le premier, modèle de mappage trouvé dans le bundle.
Nous avons maintenant les deux modèles de mappage qui peuvent être utilisés à chaque passage de la boucle et envoyer la méthode de migration au gestionnaire de migration. C'est ça.
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];
}
Remarques
Un modèle de mappage se termine cdm
dans le bundle.
Le magasin de destination doit être fourni et ne doit pas être le magasin source. Après une migration réussie, vous pouvez supprimer l'ancien et renommer le nouveau.
J'ai apporté quelques modifications au modèle de données après la création des modèles de mappage, ce qui a entraîné des erreurs de compatibilité, que je n'ai pu résoudre qu'en recréant les modèles de mappage.