Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox
EN CHANTIER
Cette page n'est pas terminée et est en cours d'écriture.

Les @property en Objective-C 2.0

Compatible iOS 3. Cliquer pour en savoir plus sur les compatibilités.Compatible iOS 4. Cliquer pour en savoir plus sur les compatibilités.Par O.Halligon (AliGator), le 26 octobre 2010

Introduction

Ce tutoriel a pour but de clarifier comment fonctionnent les @property dans le langage Objective-C ainsi que la “syntaxe pointée” pour accéder aux propriétés d'un objet

Prérequis

Pour pouvoir suivre ce tutoriel vous devez déjà avoir des notions d'Objective-C et de Programmation Orientée Objet, en particulier sur les accesseurs et idéalement la gestion mémoire.

Contexte

Lorsque l'on déclare des variables d'instance, il est souvent nécessaire de prévoir des accesseurs (un “setter” pour modifier la valeur de la variable et un “getter” pour récupérer la valeur de cette variable, depuis l'extérieur de la classe). Cette partie du code étant très répétitive plus le nombre de variable d'instance grandit, elle devient vite rébarbative. C'est là que les @property permettent de grandement simplifier le code

Présentation des propriétés

Principe et comparaison avec la méthode "manuelle"

Depuis Objective-C 2.0, il existe maintenant les mots clés @property et @synthesize qui simplifient la déclaration et l'implémentation des accesseurs.

Le principe de base est simple :

  • le mot clé @property permet de remplacer la déclaration des accesseurs (setter et getter) dans l'@interface de la classe
  • le mot clé @synthesize permet d'automatiser la génération du code de ces accesseurs dans la partie @implementation de la classe

Types simples (assign)

Ainsi, pour une variable d'instance d'un type simple comme “int”, si avant on devait écrire :

@interface MaClasse : NSObject {
  int maVariable;
}
-(int)maVariable; // getter
-(void)setMaVariable:(int)newVal; // setter
@end
 
@implementation MaClasse
-(int)maVariable {
  /* ce getter se contente de retourner la valeur de maVariable */
  return maVariable;
}
-(void)setMaVariable:(int)newVal {
  /* ce setter se contente d'affecter la valeur de maVariable à la valeur demandée */
  maVariable = newVal;
}
@end

Avec les @property et @synthesize, nous pouvons maintenant coder la même chose avec ce code équivalent :

@interface MaClasse : NSObject {
  int maVariable;
}
@property(nonatomic, assign) int maVariable;
@end
 
@implementation MaClasse
@synthesize maVariable;
@end

Objets Cocoa et 'retain'

L'exemple précédent est relativement simple et illustre le cas d'une propriété ayant l'attribut “assign” 1)

Cependant bien souvent lorsque la variable d'instance est d'un type descendant d'un objet Cocoa (NSString*, NSData*, NSDate*, etc…), le setter associé est supposé suivre une politique de gestion mémoire de type “retain”, c'est à dire retenir la nouvelle valeur. Dans ce cas sans les @property et @synthesize, nous coderions cela ainsi :

@interface MaClasse : NSObject {
  NSString* toto;
}
-(NSString*)toto; // getter
-(void)setToto:(NSString*)newVal; // setter
@end
 
@implementation MaClasse
-(NSString*)toto {
  /* le getter reste assez simple... */
  return toto;
}
-(void)setToto:(NSString*)newVal {
  /* ... mais le setter se complique déjà un peu */
  [newVal retain]; // retenir la nouvelle valeur
  [toto release];  // relâcher l'ancienne valeur puisqu'on va l'écraser
  toto = newVal;   // et là seulement on peut l'affecter
}
@end

Pour plus d'informations sur comment implémenter soi-même les setters et les règles à respecter (pourquoi devoir faire le retain avant de faire le release (et pas l'inverse) est important, etc), consultez la documentation Apple

Alors qu'avec les property, il nous suffira de déclarer dans le .h la propriété ainsi

@property(nonatomic, retain) NSString* toto;

et de mettre

@synthesize toto;

dans le .m et le code sera généré automatiquement.

Par cet exemple un peu plus complexe on commence à entrevoir l'avantage d'utiliser @property et @synthesize au lieu de coder nous même les getters et setters : quand ces derniers commencent à ce compliquer, @synthesize nous simplifie grandement la tâche !

Utiliser @property et @synthesize ne nous dispense pas de relâcher la variable d'instance dans le dealloc ! Il ne faut donc pas oublier de faire un [toto release] dans la méthode '-(void)dealloc' de notre classe pour libérer correctement la mémoire !

attribut 'readonly'

Lors de la déclaration d'une @property, nous pouvons également préciser que cette dernière est “readonly”. Dans ce cas seul le getter sera déclaré (et seul le code de ce dernier sera implémenté si l'on utilise @synthesize) : la propriété n'aura pas de setter associée, ne permettant pas à la variable d'instance d'être modifiée de l'extérieur

attribut 'nonatomic'

Par défaut, @synthesize génère l'implémentation du setter et du getter en prenant soin de le rendre thread-safe. En particulier, il s'assure que l'opération réalisée par le “getter” et par le “setter” sont “atomiques”, ne risquant pas d'être interrompue par un autre thread venant modifier la valeur en parallèle par exemple. Ceci est réalisé en général par la mise en place de “Mutex” ou via le mot clé @synchronize (à ne pas confondre avec @synthesize ; le mot clé @synchronize et le sujet du multithread ne sera pas abordé dans ce tutoriel)

Key-Value-Observing

Il est à noter que lorsque le code d'un setter est généré automatiquement via @synthesize, cette implémentation intègre également le code nécessaire pour gérer le KVO. Ainsi, la modification de la variable KVO-compliant 2), c'est à dire que n'importe quel autre objet de l'application peut se positionner en “observeur” de cette propriété et sera notifié automatiquement des changements de valeurs de la propriété (Design Pattern “Observer” ou “Listener”)

Utiliser une variable au nom différent

Il est tout à fait possible, quand on demande au compilateur de générer le code des setters et getters pour nous via @synthesize, de lier ces accesseurs à une variable d'instance d'un nom différent que celui de la propriété. Il suffit pour cela d'utiliser la syntaxe ”@synthesize prop = var;” au lieu de juste ”@synthesize prop”. Le setter “setProp” va alors affecter la variable var avec la nouvelle valeur, et le getter “prop” va retourner la valeur de var

@interface MaClasse : NSObject {
  NSString* totoVar;
}
@property(nonatomic, retain) NSString* totoProp;
@end
 
@implementation MaClasse
@synthesize totoProp=totoVar;
@end

Combiner les différentes possibilités

Même s'il vous est possible d'utiliser @synthesize pour générer automatiquement le code des @property comme nous l'avons vu, cela n'est en rien une obligation. Il est même possible de combiner les deux, c'est à dire d'implémenter le setter, et d'utiliser @synthesize qui se chargera de ne générer le code que pour le ou les accesseurs que vous n'avez pas implémenté vous-même, dans ce cas le getter.

Les différentes combinaisons possibles par l'exemple

La notation pointée

Conclusion

Tout ce qui est fait avec les @property et la notation pointée peut tout à fait être fait sans. Mais cette notation simplifie tout de même grandement à la fois la lecture du code.

En savoir plus

1) attribut que l'on peut omettre car c'est le type par défaut, mais qu'il est bon de préciser tout de même lors de la déclaration de la @property pour clarifier la lecture du code
2) Voir la documentation Apple : Key-Value Observing