Suite à des demandes et des problèmes rencontrés sur certains projets, je me suis intéressé rapidement à la migration de modèle core data, pour éviter une erreur d’incompatibilité et un crash au lancement de l’application mise à jour (cf erreur ci-dessous)
2011-05-08 19:59:45.497 articleMigration[81794:207] Unresolved error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x59384a0 {metadata=<CFBasicHash 0x59378b0 [0x100e400]>{type = immutable dict, count = 6,
entries =>
0 : <CFString 0x5937fc0 [0x100e400]>{contents = "NSStoreModelVersionIdentifiers"} = <CFArray 0x59380a0 [0x100e400]>{type = immutable, count = 0, values = ()}
2 : <CFString 0x5938010 [0x100e400]>{contents = "NSStoreModelVersionHashesVersion"} = <CFNumber 0x5903600 [0x100e400]>{value = +3, type = kCFNumberSInt32Type}
3 : <CFString 0xe2a720 [0x100e400]>{contents = "NSStoreType"} = <CFString 0xe2a8f0 [0x100e400]>{contents = "SQLite"}
4 : <CFString 0x5938040 [0x100e400]>{contents = "NSPersistenceFrameworkVersion"} = <CFNumber 0x5937ce0 [0x100e400]>{value = +320, type = kCFNumberSInt64Type}
5 : <CFString 0x5938070 [0x100e400]>{contents = "NSStoreModelVersionHashes"} = <CFBasicHash 0x5938110 [0x100e400]>{type = immutable dict, count = 1,
entries =>
0 : <CFString 0x5937b50 [0x100e400]>{contents = "Event"} = <CFData 0x59380c0 [0x100e400]>{length = 32, capacity = 32, bytes = 0x5431c046d30e7f32c2cc809958add1e7 ... 846e97d7af01cc79}
}
6 : <CFString 0xe2a8b0 [0x100e400]>{contents = "NSStoreUUID"} = <CFString 0x5937e50 [0x100e400]>{contents = "C191B893-6C37-46DE-9448-F2DC476D7284"}
}
, reason=The model used to open the store is incompatible with the one used to create the store}, {
metadata = {
NSPersistenceFrameworkVersion = 320;
NSStoreModelVersionHashes = {
Event = <5431c046 d30e7f32 c2cc8099 58add1e7 579ad104 a3aa8fc4 846e97d7 af01cc79>;
};
NSStoreModelVersionHashesVersion = 3;
NSStoreModelVersionIdentifiers = (
);
NSStoreType = SQLite;
NSStoreUUID = "C191B893-6C37-46DE-9448-F2DC476D7284";
};
reason = "The model used to open the store is incompatible with the one used to create the store";
}
Pour des raisons de sécurités, et de stabilité, une fois l’application installée avec une version d’un modèle, si celui-ci est altéré, l’application refusera de ce lancer.
Pour répondre à cette erreur on va voir une solution simple et sans trop de code.
Premier point, si ce n’est pas le cas, transformez le fichier .xcdatamodel du projet en xcdatamodeld (xcdatamodel versionné), sous xcode 3 une fois l’éditeur de core data ouvert, dans design > data modèle > add Model version. Penser aussi à changer dans l’app delegate ou votre méthode :
(NSManagedObjectModel *)managedObjectModel
Le type de fichier de mom en momd.
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel_ != nil) {
return managedObjectModel_;
}
NSString *modelPath = [[NSBundle mainBundle] pathForResource:@"articleMigration" ofType:@"momd"];
NSURL *modelURL = [NSURL fileURLWithPath:modelPath];
managedObjectModel_ = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return managedObjectModel_;
}
Sur un projet iPhone d’exemple avec un modèle basique on a un modèle qui ressemble à ça :
Je veux versionner mon modèle pour que les utilisateurs ayant ce modèle installé puissent lancer sans crash ma v2.
/!\ Je vais commencer par faire une copie de mon modèle !! /!\
Je crée un nouveau fichier de type xcdatamodel je le nome articleMigrationV1 (remarquez le V1) je copie le contenu de mon modèle actuel (petit point vert) dans ce fichier.
J’ai donc en l’état 2 datamodel identiques.
Si vous voulez vérifier tout à l’heure que le modèle crash bien quand on le modifie maintenant est le bon moment pour faire un premier build and run.
Le datamodel possédant le petit point vert, mon datamodel principal, je vais lui apporter les modifications que je désire, ici une entité supplémentaire.
Si j’essaye de lancer l’app après la modif, si j’avais déjà installé l’application, crash , cf erreur plus haut.
La solution
En 2 points.
On va créer un fichier de mapping. Pour faire comprendre les modifications apportées au modèle à notre application. C’est assez simple. Voir automatique.
On va créer un nouveau fichier de mapping :
À la création du fichier, il va falloir définir un modèle d’origine et d’arrivée
On va placer notre fichier articleMigrationV1 (remarquez encore le V1) donc notre modèle qui représente le modèle avant nos modifications dans le SOURCE modèle , ok ?
Et donc notre modèle à petit point vert dans le modèle de DESTINATION.
Le plus dur est fait. On a normalement un fichier xcmappingmodel créé :
Maintenant, il va falloir dire à notre storecoordinator que l’on a plusieurs versions du modèle et qu’il faudrait qu’il les gère tout seul.
Pour cela on va rajouter des options.
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
À notre méthode :
addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]
Ça nous donne :
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"TheLibrary.sqlite"];
NSError *error = nil;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
return persistentStoreCoordinator_;
}
Si l’on retente un build & run, pas de problème l’application se lance.
Limitation et considération
En phase de développement, c’est souvent assez pénible d’expliquer à un client que pour tester l’application il doit la supprimer avant de la réinstaller et 3 fois sur 10 (quand on est chanceux) ce n’est pas fait. Alors vous me direz que le modèle devrait être défini dès le départ et ne plus changer, je vous répondrai mais oui bien sur.
Cela peut donc s’avérer pratique d’avoir toujours le même modèle de départ, si l’application n’est jamais supprimée et installée une seule fois au départ, on peut faire évoluer notre modèle « en cours » en gardant un seul fichier de mapping que l’on fait lui aussi évoluer.
Par contre, je conseille fortement pour le déploiement sur l’App Store d’avoir un seul modèle dans la version 1, pour les raisons qui suivent.
J’ai une v1 sur l’App Store, je passe en v2 et je modifie mon modèle, la manipulation au dessus est toujours valable. Mais si je passe quelque temps après en v3, encore une fois avec modification du modèle, je ne suis pas certain du modèle installé sur l’iPhone de l’utilisateur, est-ce la v2(pour ceux qui on installé mon app directement en v2), ou la v1(pour mes premiers utilisateurs) ? Je dois donc faire un mapping, de la v1 à la v2 mais aussi de la v2 à la v3 et de la v1 à la v3. On comprend vite que c’est exponentiel.
J’espère que cet article vous aura été utile !






[...] Versioning facile de coreData | Bcharp ios, cocoa & co Suite à des demandes et des problèmes rencontrés sur certains projets, je me suis intéressé rapidement à la migration de modèle core data, pour éviter une erreur d'incompatibilité et un crash au lancement de l'application … Source: http://www.bcharp.fr [...]