Mon application iPhone doit migrer son magasin de données principal et certaines bases de données sont assez volumineuses. La documentation d'Apple suggère d'utiliser "plusieurs passes" pour migrer les données afin de réduire l'utilisation de la mémoire. Cependant, la documentation est très limitée et n'explique pas très bien comment faire cela. Quelqu'un peut-il me montrer un bon exemple ou expliquer en détail le processus pour y parvenir?
85
Réponses:
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 queNSString
.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 { // 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; }
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 leinitWithContentsOfURL:
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.
la source
Ces questions sont liées:
Problèmes de mémoire lors de la migration de grandes banques de données CoreData sur iPhone
Migration de données de base à passes multiples par blocs avec iOS
Pour citer le premier lien:
la source
Supposons que votre schéma de base de données comporte 5 entités, par exemple personne, étudiant, cours, classe et inscription pour utiliser le type d'exemple standard, où l'étudiant sous-classe la personne, la classe met en œuvre le cours et l'inscription rejoint la classe et l'étudiant. Si vous avez apporté des modifications à toutes ces définitions de table, vous devez commencer par les classes de base et progresser. Ainsi, vous ne pouvez pas commencer par convertir les inscriptions, car chaque enregistrement d'inscription dépend de la présence de la classe et des étudiants. Ainsi, vous commenceriez par migrer uniquement la table Person, en copiant les lignes existantes dans la nouvelle table et en remplissant les nouveaux champs (si possible) et en supprimant les colonnes supprimées. Effectuez chaque migration à l'intérieur d'un pool de libération automatique, de sorte qu'une fois que cela est fait, votre mémoire est de retour pour démarrer.
Une fois la table Person terminée, vous pouvez convertir la table Student. Passez ensuite au cours puis à la classe, et enfin à la table d'inscription.
L'autre considération est le nombre d'enregistrements, si comme Person avait mille lignes, vous devriez, toutes les 100 environ, exécuter l'équivalent NSManagedObject d'une version, qui consiste à indiquer le contexte de l'objet géré [moc refreshObject: ob mergeChanges: NON]; Réglez également votre minuterie de données obsolètes à un niveau bas, de sorte que la mémoire soit souvent vidée.
la source