Transcript
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