16
Simplifiez-vous CoreData Avec MagicalRecord CocoaHeads Rennes #13 Olivier Halligon Septembre 2013

CocoaHeads Rennes #13 : Magical Record

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

Page 1: CocoaHeads Rennes #13 : Magical Record

Simplifiez-vous CoreDataAvec MagicalRecord

CocoaHeads Rennes #13

Olivier Halligon

Septembre 2013

Page 2: CocoaHeads Rennes #13 : Magical Record

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)

Page 3: CocoaHeads Rennes #13 : Magical Record

Exemple de modèle de données

Page 4: CocoaHeads Rennes #13 : Magical Record

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

Page 5: CocoaHeads Rennes #13 : Magical Record

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

Page 6: CocoaHeads Rennes #13 : Magical Record

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); }}

Page 7: CocoaHeads Rennes #13 : Magical Record

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;}

Page 8: CocoaHeads Rennes #13 : Magical Record

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

Page 9: CocoaHeads Rennes #13 : Magical Record

- (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];

Page 10: CocoaHeads Rennes #13 : Magical Record

Démo

Page 11: CocoaHeads Rennes #13 : Magical Record

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 !

Page 12: CocoaHeads Rennes #13 : Magical Record

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:

Page 13: CocoaHeads Rennes #13 : Magical Record

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:

Page 14: CocoaHeads Rennes #13 : Magical Record

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:

Page 15: CocoaHeads Rennes #13 : Magical Record

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

Page 16: CocoaHeads Rennes #13 : Magical Record

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