Mon application iPhone doit migrer son magasin de données de base 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 procéder. Quelqu'un peut-il me pointer vers un bon exemple, ou expliquer en détail le processus de suppression?
J'ai compris ce que Apple astuces dans leur documentation . C'est en fait très facile mais un long chemin à parcourir avant qu'il ne soit évident. Je vais illustrer l'explication avec un La situation initiale est la suivante:
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 frappes 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:
La différence est la suivante: l'entité Event a disparu et nous en avons deux nouvelles. Un qui stocke un horodatage en tant que double
et le second qui devrait 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. Cela se traduit par deux fois les valeurs chacune comme 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 également la première partie de la réponse à votre question. Nous effectuerons la migration en deux étapes, car la migration des entrées 2k prend beaucoup de temps et nous aimons garder une empreinte mémoire faible.
Vous pouvez même aller de l'avant et diviser ces modèles de mappage pour migrer uniquement les plages des entités. Supposons que nous ayons un million d'enregistrements, cela peut bloquer tout le processus. Il est possible de réduire les entités récupérées avec un prédicat de filtre .
Nous créons le premier modèle de cartographie comme celui-ci:
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
La migration en plusieurs passes n'a pas besoin de politiques de migration d'entité personnalisées, mais nous le ferons pour obtenir un peu plus de détails pour cet exemple. Nous ajoutons donc une stratégie personnalisée à l'entité. Il s'agit toujours d'une sous-classe de NSEntityMigrationPolicy
.
Cette classe de règles implémente certaines méthodes pour réaliser notre migration. Cependant, c'est simple dans ce cas, nous devrons donc implémenter une seule méthode: createDestinationInstancesForSourceInstance:entityMapping:manager:error:
.
L'implémentation ressemblera à ceci:
#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;
}
Je vais sauter la partie pour configurer 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 passe-partout pour l'instant. Si vous en avez besoin, je posterai ici. Il peut être trouvé à Personnalisation du processus de migration c'est juste 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 classe NSMappingModel
mappingModelFromBundles:forSourceModel:destinationModel:
nous utiliserons le initWithContentsOfURL:
car la méthode de classe ne renverra 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];
}
Notes
Un modèle de mappage se termine par cdm
dans le bundle.
Le magasin de destination doit être fourni et ne doit pas être le magasin source. Vous pouvez, après une migration réussie, 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'avec la recréation des modèles de mappage.
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 en plusieurs passes en morceaux 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 consiste à diviser votre migration par type d'entité, c'est-à-dire à créer plusieurs modèles de mappage, chacun migrant un sous-ensemble des types d'entité de la modèle de données complet.