Exemple ou explication de la migration des données de base avec plusieurs passes?

85

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?

Jason
la source
avez-vous rencontré des problèmes de mémoire en fait? Votre migration est-elle légère ou souhaitez-vous utiliser un NSMigrationManager?
Nick Weaver
Oui, la console GDB a montré qu'il y avait des avertissements de mémoire, puis l'application se bloque en raison d'une mémoire limitée. J'ai essayé à la fois la migration légère et NSMigrationManager, mais en ce moment, j'essaie d'utiliser NSMigrationManager.
Jason
ok, pouvez-vous entrer un peu plus en détail ce qui a changé?
Nick Weaver
enfin, j'ai découvert, lisez ma réponse.
Nick Weaver
Bonjour Jason, pourriez-vous résoudre le problème dans la question?
Yuchen Zhong

Réponses:

174

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

entrez la description de l'image ici entrez la description de l'image ici

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:

entrez la description de l'image ici

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 doubleet 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 entrez la description de l'image ici

2. Choisissez un nom, j'ai choisi StepOne

3. Définir le modèle de données source et destination

entrez la description de l'image ici

Modèle de cartographie, première étape

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

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.

entrez la description de l'image ici

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 NSMappingModelclasse, 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 cdmdans 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.

Nick Weaver
la source
59
Putain de merde, c'est compliqué. À quoi pensait Apple?
aroth
7
Je ne sais pas, mais chaque fois que je pense que les données de base sont une bonne idée, j'essaye de trouver une solution plus simple et plus maintenable.
Nick Weaver
5
Merci! C'est une superbe réponse. Cela semble compliqué, mais ce n'est pas si grave une fois que vous avez appris les étapes. Le plus gros problème est que la documentation ne vous le précise pas comme ça.
bentford
2
Voici le lien mis à jour vers la personnalisation du processus de migration. Il a bougé depuis la rédaction de cet article. developer.apple.com/library/ios/documentation/Cocoa/Conceptual/…
user1021430
@NickWeaver comment déterminez-vous destinationStoreURL? Êtes-vous en train de le créer ou il est créé par le système de données de base pendant le processus de migration ????
dev gr
3

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:

Ceci est discuté dans la documentation officielle dans la section "Passes multiples", mais il semble que leur approche suggérée soit de diviser votre migration par type d'entité, c'est-à-dire de créer plusieurs modèles de mappage, chacun migrant un sous-ensemble des types d'entités depuis le modèle de données complet.

occulus
la source
1
Merci pour les liens. Le problème est que personne n'explique en détail comment le configurer en plusieurs passes. Comment dois-je configurer plusieurs modèles de cartographie pour que cela fonctionne efficacement?
Jason
-5

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.

Grand Schtroumpf
la source
Alors, suggérez-vous essentiellement d'avoir un nouveau schéma de données de base qui ne fait pas partie de l'ancien schéma, et de copier les données dans le nouveau schéma à la main?
Jason
-1 Le mappage manuel de votre base de données n'est pas nécessaire. Vous pouvez migrer les bases de données déployées à l'aide d'une migration légère ou avec des MappingModels explicites.
bentford