Cocoaheads Rennes #3 : Bien coder sur iOS

Preview:

DESCRIPTION

Slides de la présentation "Bien coder sur iOS" de la session des CocoaHeads Rennais du 08 septembre 2011. Présentation assurée par Julien Quéré, co-organisateur.

Citation preview

Bien coderSur iOS

Julien QUEREjulien@cocoaheads.fr

CocoaHeads Rennes8 septembre 2011

Pour qui ? Pourquoi ?Pour l’utilisateur

✓Compréhensibilité

✓Flexibilité

✓Réutilisabilité

‣ Maintenance et évolutions plus aisés

‣ Projets plus rapides

➡Gain de temps

➡Gain en réactivité

Pour le futur✓ Performance

✓ Stabilité

‣ Moins de bugs

‣ Moins de retours

‣ Une meilleure expérience utilisateur/client

➡ Gain de temps

➡ Gain en crédibilité

J.

« L’important, ce n’est pas les règles, c’est le fait d’en avoir »

Coding guidelines

Formatage- (NSURL*) url {! NSString * url = [NSString stringWithString:kWDGdirectionsBaseURL];! NSDictionary * parameters = [self parameters];!! if(parameters)! {! BOOL isFirstKey = YES;! ! ! ! NSString * separator = @"?";! !! ! ! for (NSString * key in [parameters allKeys]) {! ! ! url = [url stringByAppendingFormat:@"%@%@=%@", separator, key, [parameters valueForKey:key]];! ! !! ! ! if(isFirstKey)! ! {! ! ! isFirstKey = NO;! ! ! ! separator = @"&";! ! ! }! ! }! ! }! ! ! ! ! url = [url stringByAddingPercentEscapesUsingEncoding:! ! NSUTF8StringEncoding];! return [NSURL URLWithString:url];}

Formatage- (NSURL*) url {! NSString * url = [NSString stringWithString:kWDGdirectionsBaseURL];! NSDictionary * parameters = [self parameters];!! if(parameters) {! ! BOOL isFirstKey = YES;! ! NSString * separator = @"?";! !! ! for (NSString * key in [parameters allKeys]) {! ! ! url = [url stringByAppendingFormat:@"%@%@=%@", separator, key, [parameters valueForKey:key]];! ! !! ! ! if(isFirstKey) {! ! ! ! isFirstKey = NO;! ! ! ! separator = @"&";! ! ! }! ! }! }! url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];! return [NSURL URLWithString:url];}

Formatage

➡ Un code propre = code compréhensible

➡ Surveiller: l’indentation, le placement des délimiteurs, ...

NommageIl faut pouvoir facilement différencier:

➡ Une variable statique

➡ Une variable d’instance

➡ Un attribut

➡ Une variable de classe

➡ Une variable locale

La règle de 3

➡ Je développe une routine une fois

➡ Seconde fois: je me pose des questions

➡ Troisième fois: je factorise

- (void) displayTopTweets:(NSArray*)tweets {

!

!

! !

! !}

! if([tweets count] == 4) {! ! [tweetButton1_.titleLabel setNumberOfLines:2];! ! [tweetButton1_ setTitle:[tweets objectAtIndex:0]! ! ! ! ! forState:UIControlStateNormal];! ! [tweetButton1_ setTitle:[[tweets objectAtIndex:0] uppercaseString]! ! ! ! ! forState:UIControlStateHighlighted];! ! [tweetButton1_ setHidden:NO];!! ! [tweetButton2_.titleLabel setNumberOfLines:2];! ! [tweetButton2_ setTitle:[tweets objectAtIndex:1]! ! ! ! ! forState:UIControlStateNormal];! ! [tweetButton2_ setTitle:[[tweets objectAtIndex:1] uppercaseString]! ! ! ! ! forState:UIControlStateHighlighted];! ! [tweetButton2_ setHidden:NO];! !! ! [tweetButton3_.titleLabel setNumberOfLines:2];! ! [tweetButton3_ setTitle:[tweets objectAtIndex:2]! ! ! ! ! forState:UIControlStateNormal];! ! [tweetButton3_ setTitle:[[tweets objectAtIndex:2] uppercaseString]! ! ! ! ! forState:UIControlStateHighlighted];! ! [tweetButton3_ setHidden:NO];! !! ! [tweetButton4_.titleLabel setNumberOfLines:2];! ! [tweetButton4_ setTitle:[tweets objectAtIndex:3]! ! ! ! ! forState:UIControlStateNormal];! ! [tweetButton4_ setTitle:[[tweets objectAtIndex:3] uppercaseString]! ! ! ! ! forState:UIControlStateHighlighted];! ! [tweetButton4_ setHidden:NO];!!! }

- (void) displayTopTweets:(NSArray*)tweets {

!

!

! !

! !

! ! ! [button.titleLabel setNumberOfLines:2];! ! ! [button setTitle:tweet! ! ! ! ! forState:UIControlStateNormal];! ! ! [button setTitle:[tweet uppercaseString]! ! ! ! ! forState:UIControlStateHighlighted];! ! ! [button setHidden:NO];

! NSArray * buttons = [NSArray arrayWithObjects:tweetButton1_, tweetButton2_, ! ! ! ! ! ! tweetButton3_, tweetButton4_, nil];

! if([buttons count] == [tweets count]) {! ! for (NSUInteger i = 0; i < [buttons count]; i++) {

! ! ! ! ! }! }

! ! ! UIButton * button = [buttons objectAtIndex:i];! ! ! NSString * tweet = [tweets objectAtIndex:i];

}

Utiliser des constantes

➡ Pas de valeur numérique / textuelle «en dur»

➡ Utiliser des constantes

➡ Utiliser un fichier de locales

Définir ses propres guidelines

➡ Prendre le temps et du recul

➡ Intégrer toute l’équipe dans le processus

➡ Ne pas hésiter à se baser sur des guidelines existantes

http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml

Google Objective-C Style Guide

Jean-Pierre Florian

« Quand on est pauvre, on n'a que la ressource d'être sage. »

Gestion intelligente des ressources

Quelles ressources ?

Batterie Echanges réseau

Mémoire

Batterie

➡ CPU

➡ Radio (Wi-Fi, Bluetooth, GPS, EDGE/3G)

➡ Accéléromètre / gyroscope

➡ Boussole

➡ Disque (stockage flash)

➡ Ecran

Pour gagner en autonomie, iOS désactive le materiel dés que possible:

C’est en réduisant l’usage de ces matériels que l’on

augmente l’autonomie

Batterie: L’exemple Core Location

➡ Solution simple: - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ! manager_ = [[CLLocationManager alloc] init];! manager_.desiredAccuracy = kCLLocationAccuracyBestForNavigation;! manager_.delegate = self;! [manager_ startUpdatingLocation];

➡Core Location va mettre à jour la position en continu ...

➡... jusqu'à ce que la batterie soit vide

➡ Core Location va mettre à jour la position en continu ...

➡ ... jusqu'à ce que la batterie soit vide

Batterie: L’exemple Core Location

➡ Solution économique:

➡ Ne lancer Core Location que quand c’est nécessaire

➡ Régler la précision avec intelligence

➡ Ne pas laisser tourner Core Location inutilement

Batterie - Mais aussi➡ Eviter tout calcul inutile

➡ Ne jamais utiliser [application setIdleTimerDisabled:YES] sans raison

➡ Eviter les accès trop fréquents au disque

➡ Ne dessiner que ce qui est nécessaire

➡ Utiliser l'accéléromètre / gyroscope avec autant de parcimonie que Core Location

➡ Limiter les accès réseau

Le JSON est 52% moins lourd que le XML

Réseau

➡ Choisissez bien votre format !

➡ Exemple avec recherche twitter

0 Ko

5 Ko

10 Ko

14 Ko

19 Ko

Taille de la réponse

9,47 Ko

18,05 Ko

http://search.twitter.com/search.json?q=cocoaheads&result_type=mixed&count=100

Données calculées sur la moyenne de 100 appels à l’API Twitter en JSON vs XML

XML JSON

Réseau

➡ Ne prendre ce qui est nécessaire

➡ Pourquoi télécharger 100 tweets sur les CocoaHeads alors qu’on en affichera que 15 ?

➡ Astuce: utiliser la pagination

➡ Mais ne pas en abuser: éviter de trop nombreuses petites requêtes

Réseau

➡ Malheureusement, on n’a pas toujours la main sur la plateforme Web

➡ Il faut s’en accommoder

➡ Commencer la réflexion en partant du mobile dès que possible

Walter Prévost

« La mémoire, c'est comme une valise. On met toujours dedans des choses qui ne

servent à rien.»

La mémoire

La mémoire

➡ Objectif: réduire son emprunte mémoire:

➡ Consommer le moins de mémoire, le moins longtemps possible.

La mémoire

0 Mo

32 Mo

64 Mo

96 Mo

128 Mo

58 Mo

4 Mo10 Mo

12 Mo

32 Mo

12 Mo

Source: Apple TechTalks Paris 2009 Valeurs pour un iPhone 3G

Plus les appareils montent en gamme plus il y a de mémoire.

Cependant, iOS est de plus en plus gourmand avec le temps

Couche graphiqueKernelDémonsSpringboardTéléphoneDisponible

La mémoire

➡ Principales sources de « gaspillage »:

➡ Les fuites

➡ La mémoire allouée inutilement

➡ La mémoire libérée trop tard

La mémoire

Rappel: 1 retain / alloc / copy = 1 release

Gare à l’autoRelease !

➡ Tout objet en autorelease est placé dans le bassin le plus «proche»

➡ Quand le bassin est vidé, il envoie un Release à chacun des objets qu’il gère

L’autorelease très pratique, mais il faut redoubler de vigilance

Gare à l’autoRelease ! NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; TBXML * tbxml = [[TBXML alloc] initWithXMLData:xmlData]; TBXMLElement *root = tbxml.rootXMLElement; if (root) { TBXMLElement *item = [TBXML childElementNamed:@"item" parentElement:root]; while (item != nil) { // Parsing code item = [TBXML nextSiblingNamed:@"item" searchFromElement:item]; } } [tbxml release]; [pool release];

0MB

13MB

25MB

38MB

50MB

5MB

20MB

35MB

49MB

5MB 5MB 5MB

Parsing d’un fichier XML basé sur le Top 300 iTunes avec TBXML 1.4.Mesures effectuées avec instruments.

TBXML * tbxml = [[TBXML alloc] initWithXMLData:xmlData]; TBXMLElement *root = tbxml.rootXMLElement; if (root) { TBXMLElement *item = [TBXML childElementNamed:@"item" parentElement:root]; while (item != nil) {

Gare à l’autoRelease !

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; // Parsing code item = [TBXML nextSiblingNamed:@"item" searchFromElement:item]; } } [tbxml release];

[pool release];

0MB

13MB

25MB

38MB

50MB

5MB 7MB12MB 12MB

5MB 5MB 5MB5MB

20MB

35MB

49MB

5MB 5MB 5MB

Une gestion fine de l’autorelease, c’est une gestion fine de la mémoire

Parsing d’un fichier XML basé sur le Top 300 iTunes avec TBXML 1.4.Mesures effectuées avec instruments.

Le Dealloc

➡ Un dealloc vide ou incomplet: c’est la garantie de fuites mémoire.

➡ Astuce 1: A la création d’une variable d’instance, on la met de suite dans le dealloc (si il y a lieu).

➡ Astuce 2: Mettre les variable dans le même ordre dans l’interface que dans le dealloc.

➡ Ne jamais oublier le [super dealloc];

Be lazy

- (IBAction) showCredits:(UIButton*) sender {! [self displayTextPopup:credits_];}- (void) dealloc {! [credits_ release];! [super dealloc];}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self != nil) {

} return self;}

credits_ = [[NSString stringWithContentsOfFile:filePath] retain];

! ];! [self credits]

Be lazy

- (IBAction) showCredits:(UIButton*) sender {! [self displayTextPopup:}- (void) dealloc {! [credits_ release];! [super dealloc];}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self != nil) {

} return self;}- (NSString*)credits {! if(!credits_) {

! }!! return credits_;}

credits_ = [[NSString stringWithContentsOfFile:filePath] retain];

Memory warning

➡ Le memory warning signifie que le système va être à court de mémoire.

➡ Soyez courtois, et libérez tout ce que vous pouvez.

➡ Rappelez vous, le système a la License To Kill.

Memory warning

Trois options pour être tenu au courant:

➡ Dans un ViewController: - (void) didReceiveMemoryWarning;

➡ Dans l’UIApplicationDelegte:

- (void) applicationDidReceiveMemoryWarning:(UIApplication *)application;

➡ Partout ailleurs: UIApplicationDidReceiveMemoryWarningNotification

Luc Fayard

« Développement : Alliance d'une science inexacte et d'une activité humaine faillible.»

Crash et ralentissement

Crash et ralentissement

Core Data

➡ Un NSManagedObjectContext ne peux être partagé entre plusieurs threads

➡ Un NSManagedObject n’est valide que dans son NSManagedObjectContext

➡ Conséquence: il ne faut pas passer de NSManagedObject entre plusieurs threads !

Core Data

1

3

2

ObjectID

3

ObjectID

Thread 1 / MOC 1 Thread 2 / MOC 2

[monContext objectWithID:monObjectId]; NSString * monObjectId = [monObjet objectID];

Mais aussi ...

➡ Être très rigoureux sur la gestion des retain / release

➡ User et abuser du respondsToSelector:

➡ Toujours penser aux cas aux limites

➡ Instruments offre des outils pour tracer et comprendre tout ça ...

Confucius

« L'ouvrier qui veut bien faire son travail doit commencer par aiguiser ses instruments. » 

Analyser

Analyser

Ce que je voulaisfaire

Ce que je pense avoir fait

Ce que j’ai fait

Analyser

➡ Analyser, c’est comprendre ce qui a été réellement fait

➡ Instruments est l’outil parfait pour ça:

Consommationmémoire

Fuitesmémoire

Consommationd’énergie

Performances graphiques Zombies

Jean-Claude Dusse

« Je sens que, ce soir, je vais conclure »

Conclusions

Conclusions

➡ Bien coder, c’est du temps, de la crédibilité et de l’argent de gagné.

➡ Définir des règles intelligemment ... et s’y tenir !

➡ Soyez courtois avec les ressources

➡ Lisez, creusez et comprenez la documentation ...