Tour d’horizon des nouveautés du langage Objective-C

Tour d’horizon des nouveautés du langage Objective-C


L’Objective-C a connu ces dernières années différentes évolutions qui ont permis de donner un coup de jeune à un langage qui va fêter d’ici peu ses trente bougies. Pour y voir plus clair dans ces évolutions, je vous propose de (re)découvrir un bref résumé chronologique des évolutions apportées par Apple au langage  :

  • 2006 / 2007 : Apple annonce la révision 2.0 du langage lors du WWDC. Cette révision apporte de nombreuses nouveautés telles que le support des propriétés (On attend toujours pour Java …), des énumérations rapides, des déclarations optionnelles de méthodes dans les protocoles, ou bien encore des extensions de classes.
  • 2010 : Apple introduit une nouveauté comparable à la notation lambda en Java appelée Blocks. Ces blocs de codes ne sont pas de simple fonctions anonymes mais des closures: elles conservent l’accès aux données du contexte dans lequel elles ont été créées.
  • 2011 : Avec l’arrivée d’iOS 5, Apple a introduit la gestion automatique du comptage des références, également appelée ARC pour Automatic Reference Counting, qui a pour but de permettre aux développeurs de globalement s’affranchir des problématiques de gestion de la mémoire. Cette dernière évolution est un tel chamboulement que de nombreux projets ne l’ont pas encore adopté et continuent de fonctionner avec un comptage manuel des références.
  • 2012 : Apple introduit une syntaxe simplifiée via le support des Object Literals à partir d’OSX 10.8 et d’Xcode 4.4. Cette notation est cependant disponible uniquement pour les développeurs iOS utilisant le SDK 6.0.

Ces diverses évolutions ont contribué à moderniser le langage et simplifier son utilisation. Apple proposait d’ailleurs lors du dernier WWDC, deux sessions : l’une ayant pour objectif de présenter ces nouveautés, et l’autre de se familiariser avec afin de les intégrer dans les projets.

A travers différents exemples, je vous propose de découvrir certaines de ces simplifications / améliorations. Si vous souhaitez tester ces fonctionnalités vous pouvez vous équipez de la version 4.4 ou supérieur d’Xcode qui intègre la version 3.1 du compilateur Clang gérant ces nouveautés.

Définition

Un littéral est une déclaration dans le code source d’un logiciel permettant de représenter une valeur. Le littéral le plus connu est la représentation d’une chaîne de caractères dans un code source. Une simple déclaration de chaîne de caractères en Java, par exemple, permet d’instancier un objet String sans passer explicitement par un constructeur : sa construction / instanciation / initialisation est implicite. Le recours aux littéraux dans un code source a pour objectif de simplifier, et de rendre plus lisible et compréhensible un code source.

En Objective-C, la syntaxe littérale pour instancier un objet NSString est la suivante :

NSString *nickname = @"John Doe";

Vous pouvez cependant déclarer le même objet NSString via une syntaxe n’utilisant pas de littéraux :

NSString *nickname = [NSString stringWithCString:"John Doe" encoding:NSUTF8StringEncoding];

L’intérêt d’une telle notation saute tout de suite aux yeux. La déclaration d’un littéral NSString est similaire à celle d’un littéral C string standard, sauf qu’elle est préfixée par le caractère ‘@’.

Number literals

Les nouveaux littéraux NSNumber sont également de simples littéraux C standards préfixés d’un caractère ‘@’. Il est possible d’appliquer cette notation à différents types de valeurs pour obtenir des objets NSNumber. Ainsi, vous pouvez écrire les déclarations suivantes pour obtenir des objets NSNumber :

NSNumber *intNumber = @42;
NSNumber *doubleNumber = @3.1415926;
NSNumber *charNumber = @'A';
NSNumber *boolNumber = @YES;

Alors qu’il aurait fallu écrire auparavant :

NSNumber *intNumber = [NSNumber numberWithInt:42];
NSNumber *doubleNumber = [NSNumber numberWithDouble:3.1415926];
NSNumber *charNumber = [NSNumber numberWithChar:'A'];
NSNumber *boolNumber = [NSNumber numberWithBool:YES];

Cette nouvelle syntaxe supporte également des suffixes comme ‘f’ pour float ou bien ‘u’ pour les nombres non signés :

NSNumber *unsignedIntNumber = @1024u;
NSNumber *floatNumber = @3.14159f;

Ce qui équivaut à la notation historique : 

NSNumber *unsignedIntNumber = [NSNumber numberWithUnsignedInt:1024u];
NSNumber *floatNumber = [NSNumber numberWithFloat:3.14159f];

Tableaux

L’aspect concis des déclarations précédentes met bien en avant l’intérêt de l’usage des littéraux, cependant leur usage appliqué à la création de tableaux et de dictionnaires permet de convaincre définitivement.

Pour déclarer un tableau, la notation suivante était auparavant requise : 

NSArray *items = [NSArray arrayWithObjects:@"item", [NSNumber numberWithBool:YES], [NSNumber numberWith:24], nil];

Un nil devait être positionné en dernier élément de la déclaration d’un tableau. Heureusement, ce pré-requis hérité du C, n’est plus qu’un lointain souvenir, puisqu’il est maintenant possible de déclarer le même tableau avec la déclaration simplifiée suivante :

NSArray *items = @[ @"item", @YES, @24 ];

Dictionnaires

Il est également possible de créer des instances de la classe NSDictionary avec une syntaxe simplifiée. Il était auparavant nécessaire de créer un dictionnaire avec la notation suivante :

NSDictionary *options = [[NSDictionary alloc] initWithObjectsAndKeys:
       [NSNumber numberWithBool:YES], @"synchronize",
       [NSNumber numberWithInt:1000],    @"maxItems",
       @"value",                        @"key",
       nil
];

Il est maintenant possible de créer la même instance avec la déclaration suivante :

NSDictionary *options = @{
  @"synchronize":      @YES,
  @"daysToKeepBackup": @1000,
  @"key":              @"value"
};

Attention : Les littéraux de type conteneur (tableaux et dictionnaires) sont toujours immutables, si vous souhaitez modifier un tableau ou bien un dictionnaire, il est nécessaire d’en créer au préalable une copie via l’appel de la méthode : -mutableCopy

NSMutableArray *planetesMutables = [@[
  @"Mercure", @"Vénus", @"La Terre",
  @"Mars", @"Jupiter", @"Saturne",
  @"Uranus", @"Neptune"
] mutableCopy];

De même, les littéraux ne sont pas déclarables en tant que constante, il n’est donc pas possible d’écrire la déclaration suivante :

static NSArray *planetes = @[
  @"Mercure", @"Vénus", @"La Terre",
  @"Mars", @"Jupiter", @"Saturne",
  @"Uranus", @"Neptune"
];

Pour contourner ce problème, vous pouvez utiliser la déclaration suivante, qui elle, est valide :

@implementation MaClass
static NSArray *planetes;
+ (void)initialize {
  if (self == [MaClass class]) {
    planetes = @[
      @"Mercure", @"Vénus", @"La Terre",
      @"Mars", @"Jupiter", @"Saturne",
      @"Uranus", @"Neptune"
    ];
  }
}

Subscripting

Le support des littéraux de type conteneur est complété par une notation dite de subscripting qui permet d’accéder directement aux éléments d’un tableau (Indexed Subscripting) ou d’un dictionnaire (Keyed Subscripting) avec une notation de l’index du tableau ou bien de la clé du dictionnaire entre crochets, comme suit :

id value1 = someArray[0];
id value2 = someDictionary[@"key"];

Cette notation permet non seulement d’accéder aux valeurs d’un tableau ou d’un dictionnaire, mais également de les modifier. L’accès aux données d’un tableau NSArray en Objective-C peut donc se faire maintenant de la même manière qu’avec un tableau C traditionnel, il faudra cependant toujours faire attention à ne pas utiliser d’index en dehors des dimensions du tableau pour éviter de lever une exception.

L’aspect le plus intéressant de cette nouvelle notation réside dans le fait qu’elle est applicable à n’importe quel type d’objet à partir du moment où les méthodes suivantes sont implémentées :

objectAtIndexedSubscript: anIndex
setObject: newValue atIndexedSubscript: anIndex
objectForKeyedSubscript: aKey
setObject: newValue forKeyedSubscript: aKey

Boxings / Boxed Expressions

La notion de Boxing vient du fait d’encadrer une expression avec les symboles : @(). Ainsi plutôt que d’écrire l’expression :

NSNumber *two = [NSNumber numberWithInt:(1+1)];

Il devient possible d’écrire :

NSNumber *two = @(1+1);

Ou bien encore :

int foo = 25;
NSNumber *bar = @(foo);

Les Boxed expressions ne seront toutefois supportées qu’à partir de la version 3.2 du compilateur Clang. La version 3.1 ne supporte que les déclarations basées sur des constantes scalaires, comme l’exemple ci-desous déjà vu plus haut :

NSNumber *two = @2;

Les énumérations ne sont pas laissées de côté puisque l’usage des Boxed Expressions est également applicable aux énumérations. L’usage de parenthèses se justifie pleinement avec l’énumération suivante :

enum {interface, implementation, protocol};

Le compilateur serait dans l’obligation de produire une erreur en présence de l’expression suivante :

NSNumber *which = @protocol;

L’usage de parenthèses permet donc de lever toute ambiguïté :

NSNumber *which = @(protocol); // [NSNumber  numberWithInt:2];

L’utilisation des Boxed Expressions ne se limite pas aux nombres, elle fonctionne également avec les chaînes de caractères puisqu’il est possible d’écrire le code suivant qui permet de mélanger élégamment du C avec de l’Objective-C :

NSString *results = @(strstr("Hello World!",  "W"));

Propriétés

La notion de propriétés, introduite dans la version 2.0 des spécifications du langage Objective-C, offre la possibilité de déclarer de façon simplifiée les méthodes d’accès et de mutation aux champs d’une classe donnée. Ainsi dans une interface, il est possible de déclarer des propriétés de la façon suivante :

@interface Counter : NSObject {
    NSNumber *_count
}

@property (nonatomic, retain) NSNumber *count;

@end

Une implémentation manuelle serait la suivante :

@implementation Counter

- (NSNumber *)count {
    return _count;
}

- (void)setCount:(NSNumber *)newCount {
    [newCount retain];
    [_count release];
    // Make the new assignment.
    _count = newCount;
}

- (void)dealloc {
  [_count release];
  [super dealloc];
}
@end

Néanmoins l’Objective-C permet de synthétiser les méthodes d’accès et de mutation remplaçant avantageusement les déclarations précédentes par un code moins verbeux :

@interface Counter : NSObject
@property (nonatomic, strong) NSNumber *count;
@end
@implementation Counter
@synthesize count = _count;
@end

L’introduction de l’ARC (Automatic Reference Counting) déprécie (proscrit) l’usage des méthodes retain et release ce qui a pour effet de rendre le code d’autant plus simple.

On peut tout de même remarquer que le code écrit précédemment, bien que simplifié n’est pas tout à fait DRY, puisque la déclaration de la variable est présente, et dans l’interface, et dans l’implémentation, ce qui a pour effet d’introduire de la redondance. Apple a donc décidé de rendre optionnelle dans la dernière version de son compilateur la déclaration de synthétisation de la variable dans l’implémentation, et donc de réduire les répétitions inutiles.

Une fois la déclaration de synthétisation des champs de classe supprimée, le code obtenu est le suivant :

@interface Counter : NSObject
@property (nonatomic, strong) NSNumber *count;
@end
@implementation Counter
@end

Test de support des nouvelles fonctionnalités

Si vous décidez d’utiliser les nouvelles fonctionnalités citées précédemment, il peut être utile de s’assurer que ces fonctionnalités (features) sont disponibles avec la version du compilateur que vous utilisez. Ceci est particulièrement utile lorsque vous fournissez une librairie au format source qui peut être compilée et linkée avec différentes versions de compilateurs qui peuvent pour chacune d’elles supporter ou non une fonctionnalité donnée.

Si vous souhaitez tester la disponibilité d’une fonctionnalité, vous pouvez utiliser le test de préprocesseur : __has_feature.
Ainsi, si vous souhaitez tester la disponibilité d’une fonctionnalité particulière, vous pouvez utiliser les tests suivants :

  • __has_feature(objc_array_litterals) pour tester le support des littéraux pour tableaux.
  • __has_feature(objc_dictionary_literals) pour tester le support des littéraux pour dictionnaires.
  • __has_feature(object_subscripting) pour tester le support du subscripting.
  • __has_feature(objc_bool) pour tester le support des littéraux numériques.
  • __has_feature(objc_boxed_expressions) pour le support des boxed expressions.

Le test s’écrit de la façon suivante :

#if __has_feature(objc_array_literals)
   // ...
#else
   // ...
#endif

Attention, il semblerait que les boxed expressions ne soient disponibles qu’avec le compilateur Clang 3.2. Xcode 4.4 et 4.5 sont livrés avec des versions du compilateur d’Apple basé sur la version 3.1 de Clang.

Conclusion

Avec de nouvelles évolutions proposées régulièrement, Apple a pour volonté de maintenir un langage à jour et moderne. Ces évolutions permettent de gagner en productivité ainsi qu’en maintenabilité. Cependant, même si le langage a gagné en simplicité et efficacité, les possibilités d’évolutions restent nombreuses. L’arrivée le mois dernier d’iOS 6 et d’Xcode 4.5 en version finale devrait permettre d’expérimenter ces nouvelles fonctionnalités dans nos Applications !

Liens utiles

Deux très bons articles d’introduction aux littéraux en Objective-C par les talentueux auteurs / formateurs du Big Nerd Ranch :

Une cheatsheet sur les littéraux Objective-C :

La documentation officielle :

Présentations au WWDC 2012 :

Leave a Reply