Upload
cocoaheadsrns
View
3.687
Download
0
Embed Size (px)
DESCRIPTION
CoreData vous tente mais vous fait peur ? Vous trouvez le framework un peu dur à prendre en main ? Ou vous en avez marre d’écrire autant de ligne à chaque fois juste pour faire une simple récupération de vos données ? Olivier Halligon (développeur de FoodReporter) vous offrira une découverte de MagicalRecord, le framework qui va drastiquement simplifier votre code CoreData, en apportant le Design Pattern ActiveRecord (comme utilisé en Ruby) sur Objective-C.
Citation preview
Simplifiez-vous CoreDataAvec MagicalRecord
CocoaHeads Rennes #13
Olivier Halligon
Septembre 2013
CoreData : rappels
• Framework Cocoa pour iOS et OSX• Permet de gérer un graphe d’objets et sa persistance
• Gestion de relations
• Gestion des transactions, d’annulation…
• Gestion des futures (faulting)
•N’est pas une base de données relationnelle• On peut l’utiliser en InMemory-only
• Peut faire persister les données dans une base SQLite mais aussi en XML
• On peut l’utiliser comme abstraction d’un WebService (via NSIncrementalStore)
Exemple de modèle de données
Complexité de CoreData
NSAtomicStoreNSPersistent
StoreNSManagedObjectModel
NSFetc
hResul
t
Contro
ller
NSAttribute
Description
NSManagedObjectID
NSEntityMapping
NSManagedObject
NSFetch
Request
NSManaged
ObjectContex
t
NSEntityDescription
NSIncr
ementa
l
Store
NSPersistentStoreCoordinator
Complexité de CoreData
• Beaucoup de classes à prendre en main quand on commence• Dont pour la plupart le rôle semble abstrait de prime abord
• Même si ça vient avec la pratique, peu encourageant au début
• Beaucoup de lignes de code pour des opérations simples• Une dizaine de lignes rien que pour récupérer un objet dans notre graphe
• Du coup répétitif pour des actions fréquentes
• Nécessité d’utiliser des NSString pour décrire les noms d’entités à récupérer
• Pas d’autocomplétion, pas de vérification à la compilation
• Nécessité de transtyper (caster) les résultats
Exemple de Requête
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdFetching.html#//apple_ref/doc/uid/TP40002484-SW1
- (void)logAllRennesSessions{ NSManagedObjectContext *moc = [self managedObjectContext]; NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Session" inManagedObjectContext:moc]; NSFetchRequest *request = [[NSFetchRequest alloc] init]; [request setEntity:entityDescription];
// Set sort descriptor NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO]; [request setSortDescriptors:@[sortDescriptor]];
// Set predicate NSPredicate *predicate = [NSPredicate predicateWithFormat:@"city.name == %@", @"Rennes"]; [request setPredicate:predicate]; NSArray *array = [moc executeFetchRequest:request error:nil]; if (array == nil) { NSLog(@"Some error occured"); } else { NSLog(@"Sessions in Rennes: %@", array); }}
Exemple de Requête
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdFetching.html#//apple_ref/doc/uid/TP40002484-SW1
- (City*)findOrCreateCityWithName:(NSString*)cityName{ NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext]; // Find the city if it exists, create it if not NSManagedObject* foundCity = nil; NSEntityDescription* cityEntity = [NSEntityDescription entityForName:@"City" inManagedObjectContext:context]; NSFetchRequest* cityRequest = [[NSFetchRequest alloc] init]; [cityRequest setEntity:cityEntity]; NSPredicate* cityPredicate = [NSPredicate predicateWithFormat:@"name == %@", cityName]; [cityRequest setPredicate:cityPredicate]; [cityRequest setFetchLimit:1]; NSArray* cities = [context executeFetchRequest:cityRequest error:nil]; if (cities.count == 0) { foundCity = [[NSManagedObject alloc] initWithEntity:cityEntity insertIntoManagedObjectContext:context]; [foundCity setValue:cityName forKey:@"name"]; } else { foundCity = [cities objectAtIndex:0]; } return (City*)foundCity;}
MagicalRecord
• ActiveRecord pour Objective-C• Pattern bien connu des programmeurs Ruby
• Utiliser les objets du MDD directement : [Session findAll]
• Un framework «wrapper» pour faciliter votre code• Requêtes implicites, lecture plus claire
• Plus facile à prendre en main pour débuter en CoreData
• Reste utile même quand vous n’êtes plus débutant pour avoir un code concis
• N’empêche pas de continuer à utiliser les méthodes du framework Apple
- (void)logAllRennesSessions{
}
NSArray* array = [Session findByAttribute:@"city.name" withValue:@"Rennes" andOrderBy:@"date" ascending:NO]; NSLog(@"Sessions in Rennes: %@", array);
- (void)logAllRennesSessions{
}
NSManagedObjectContext *moc = [self managedObjectContext]; NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Session" inManagedObjectContext:moc]; NSFetchRequest *request = [[NSFetchRequest alloc] init]; [request setEntity:entityDescription];
// Set predicate NSPredicate *predicate = [NSPredicate predicateWithFormat:@"city.name == %@", @"Rennes"]; [request setPredicate:predicate];
// Set sort descriptor NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO]; [request setSortDescriptors:@[sortDescriptor]]; NSArray *array = [moc executeFetchRequest:request error:nil]; NSLog(@"Sessions in Rennes: %@", array);
Exemple de Requête NSManagedObjectContext *moc = [self managedObjectContext]; NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"Session" inManagedObjectContext:moc];
Avec MagicalRecord
// Set sort descriptor NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO]; [request setSortDescriptors:@[sortDescriptor]];
// Set predicate NSPredicate *predicate = [NSPredicate predicateWithFormat:@"city.name == %@", @"Rennes"]; [request setPredicate:predicate];
[Session andOrderBy:@"date" ascending:NO];ByAttribute:@"city.name" withValue:@"Rennes" NSArray* array = NSLog(@"Sessions in Rennes: %@", array);
NSArray *array = [moc executeFetchRequest:request error:nil]; NSLog(@"Sessions in Rennes: %@", array);
find
NSFetchRequest *request = [[NSFetchRequest alloc] init]; [request setEntity:entityDescription];
Démo
Comparatif
Nombre de lignes de code CoreData MagicalRecordConfiguration CoreData dans l’AppDelegate 70~90 1
logAllSessions 12 1
emptyGraphObject 17 3
newSessionWithSubject:date:summary:lecturer: 10 6
findOrCreateCityWithName: 24 7
findOrCreatePersonWithFirstName: 25 9
buildFetchedResultsController 24 1
findSimilarSessions 13 2
Total 230~250 65
Avant Après
70% de code en moins !
Possibilités offertes par MagicalRecord
• Catégories pour avoir des méthodes de commodité• Récupérer tous les objets qui suivent un prédicat
• findAll, findAllSortedBy:ascending:, findAllByAttribute:withValue:orderBy:ascending:, findAllSortedBy:ascending:withPredicate:, …
• Compter le nombre d’entités• countOfEntities, countOfEntitiesWithPredicate:
• Récupérer un seul objet• findFirst, findFirstWithPredicate:, findFirstWithPredicate:sortedBy:ascending: findFirstByAttribute:withValue:
• Créer et supprimer des entités• createEntity, deleteEntity, deleteAllMatchingPredicate:, truncateAll, …
• Construire des requêtes• createFetchRequest, requestAll, requestAllWithPredicate:, requestAllWhere:isEqualTo:, …
• Utiliser les Fetch Results Controllers• fetchAllSortedBy:ascending:withPredicate:groupBy:delegate:
Possibilités offertes par MagicalRecord
• Faciliter la gestion des Contextes• Toutes les méthodes de MR ont une variante avec et sans contexte• Sans contexte précisé, MR utilise son defaultContext
• Un ManagedObjectContext par défaut, ainsi qu’un contexte par thread• [NSManagedObjectContext defaultContext], [NSManagedObjectContext contextForCurrentThread]
• Passer un ManagedObject d’un thread à l’autre, d’un contexte à l’autre• NSManagedObject *objectOnThreadTwo = [objectOnThreadOne inThreadContext];
• NSManagedObject *objectInCtxTwo = [objectInCtxOne inContext:otherContext];
• Création simple d’un contexte fils à la demande• contextWithParent:, contextThatPushesChangesToDefaultContext
• Gestion des sauvegardes• En cascade jusqu’au PersistentStore (ex: app en bkg) : saveToPersistentStoreWithCompletion:• Sur un contexte local via une API avec des blocks : saveWithBlock:completion:, saveInBackgroundWithBlock:…
context WithParent:
save:
Possibilités offertes par MagicalRecord
• Simplifier la phase d’initialisation• Initialisation en une ligne
• [MagicalRecord setupCoreDataStack];
• Très pratique pour les Tests Unitaires
• InMemoryStore : toujours partir sur une base vide, ne pas polluer la base de prod• [NSBundle bundleForClass:self] pour fonctionner même en phase de TU• [MagicalRecord setupCoreDataStackWithInMemoryStore];
• Possibilité de créer un NSManagedObjectModel unifié d’après les MOM du bundle• defaultManagedObjectModel, mergedObjectModelFromMainBundle
• Support d’iCloud• setupCoreDataStackWithiCloudContainer:localStoreNamed:
• setupCoreDataStackWithiCloudContainer:contentNameKey:localStoreNamed:cloudStorePathComponent:completion:
MagicalRecord avec CocoaPodspod install
pod update
xcodeproj "MRDemo"platform :ios, '5.0'
pod "MagicalRecord", "~>2.0"
Podfile
-(BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)options{ [MagicalRecord setupCoreDataStack]; … return YES;}
AppDelegate.m
#ifdef __OBJC__#define MR_SHORTHAND#define MR_ENABLE_ACTIVE_RECORD_LOGGING 0#import "CoreData+MagicalRecord.h"#endif
Pods-MagicalRecord-prefix.pch
#ifdef __OBJC__#define MR_SHORTHAND#import "CoreData+MagicalRecord.h"#endif
YourApp-Prefix.pch
Références• CoreData Programming Guide
• https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html
•MagicalRecord• https://github.com/magicalpanda/MagicalRecord (don’t forget the wiki)
• http://nshipster.com/core-data-libraries-and-utilities/
• CocoaPods• http://cocoapods.org/ & http://docs.cocoapods.org/
• https://github.com/CocoaPods/Specs (don’t forget the wiki)
• Code source de la démo• https://github.com/CocoaHeads-Rennes/13-MagicalRecord