Modèle de conception : KVO pour Key Value Observing. Apprenez à suivre vos objets

Image non disponible

Le modèle KVO forme avec KVCPrésentation du principe Key Value Code l'un des principaux socle de l'environnement de développement Cocoa.

Si KVC se rapproche très largement des règles de codage définies en Java pour les Java Beans (1) , on peut considérer que le modèle KVO est le pendant du modèle d'événements en Java.

La similitude s'arrête là car le modèle KVO est bien plus simple.

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Google Bookmarks ! Facebook Digg del.icio.us Yahoo MyWeb Blinklist Netvouz Reddit Simpy StumbleUpon Bookmarks Share on Google+ 

I. Principe Général

Le modèle de conception Key Value Observing(2) permet de suivre les changements sur les valeurs des propriétés d'objets.

Si l'on parle ici de couple clef-valeur c'est pour une raison simple : pour son fonctionnement, le mécanisme KVO s'appuie largement sur le modèle KVC.

On considère au moins deux acteurs pour la mise en oeuvre de ce modèle de conception :

  1. L'objet observé qui expose un ensemble d'attributs sous la forme d'une liste de noms : les clefs. La valeur de ces attributs est donc assimilée à la valeur des clefs.
  2. L'objet observateur qui décide de suivre les changements intervenus sur un objet particulier.

II. Protocoles

Les responsabilités entre les deux objets sont donc définies comme suit :

Objet Observé
  1. Implémente le modèle de conception KVC. Les attributs seront accédés en utilisant le protocole NSKeyValueCoding
  2. Permet de s'abonner aux modifications de l'objet en respectant le protocole NSKeyValueObserving.
Objet observateur
  1. S'abonne à un objet en utilisant le protocole NSKeyValueObserving
  2. Est notifié d'un changement par son implémentation du même protocole.

Encore un protocole à implémenter ?

En fait non, pas pour les objets observés. C'est la classe NSObject qui propose une implémentation du protocole NSKeyValueObserving.

Ainsi tous les objets dont la classe dérive de NSObject peuvent être suivi par d'autres objets observateurs. Pour s'abonner un objet doit simplement en faire la demande en appelant la méthode suivante sur l'objet observé :

 
Sélectionnez
- (void) addObserver: (NSObject *) anObserver
          forKeyPath: (NSString *) keyPath
             options: (NSKeyValueObservingOptions) options
             context: (void *) context

Pour ne plus recevoir les notifications il suffit de résilier son abonnement avec la méthode suivante :

 
Sélectionnez
- (void) removeObserver: (NSObject *) anObserver
             forKeyPath: (NSString *) keyPath

Seul les observateurs doivent implémenter au moins une méthode du protocole, celle qui permet d'être averti d'un changement :

 
Sélectionnez
- (void) observeValueForKeyPath: (NSString *) keyPath
                       ofObject: (id) object
                         change: (NSDictionary *) change
                        context: (void *) context

III. Un exemple pratique

Si les exemples les plus classiques utilisent une interface graphique pour illustrer l'utilisation de KVO je vais vous proposer un exemple plus simple sous la forme d'une application en ligne de commande.

Même si nous en sommes très loin, considérons le petit exemple que je vous propose comme les briques élémentaires d'un outil de GPS.

Nous allons implémenter deux classes :

  • Une classe Position qui donne la position d'un objet dans l'espace par un triplet de coordonnées : latitude, longitude et altitude.
  • Une classe de suivi ValueTracker (notre observateur) dont le rôle sera de suivre les déplacements d'un objet.

Pour faire une classe un peu générique notre classe de suivi peut s'abonner aux évolutions de n'importe quel objet et ne se limite donc pas à l'utilisation avec un objet Position.

Un objet ValueTracker connaît :

  • l'objet qu'il suit : la source ;
  • le nom de la propriété à laquelle il est abonné : valueKey
  • la dernière valeur en date : value

Le corps de l'exemple va ensuite utiliser ses deux classes pour construire une position et un objet de suivi et montrer comment l'observation vient s'intégrer dans cet exemple.

IV. Mise en oeuvre de l'exemple

Pour commencer vous devez créer un projet de type « Foundation Tool » dans le groupe « Command Line Utility ». Cela vous construit un projet utilisable en ligne de commande comprenant au moins le fichier source principal avec une fonction main().

Vous pouvez récupérer directement le source de cet exemple dans le fichier archive joint avec cet article.

Image non disponible
Archive du projet pour XCode 3

IV-A. La classe position

Commencez par implémenter la classe position en suivant l'interface indiquée ci-dessous :

Position.h
Sélectionnez
@interface Position : NSObject {
   float latitude;
   float longitude;
   float altitude;
}

@property(assign, readwrite) float latitude;
@property(assign, readwrite) float longitude;
@property(assign, readwrite) float altitude;

- (NSString *) description;

@end

Dans votre implémentation, seule la méthode description devrait être à votre charge. Les accesseurs et modificateurs étant générés par le compilateur à l'aide de la directive @synthetize. (3)

Position.m
Sélectionnez
@implementation Position

@synthesize latitude, longitude, altitude;

- (NSString *) description
{
   NSString * msg = [NSString
                     stringWithFormat:@"Position(lat=%f ; long=%f, alt=%f)",
                     latitude,
                     longitude,
                     altitude];
   return msg;
}

@end

IV-B. La classe ValueTracker, sans KVO

Nous allons commencer par le comportement fonctionnel de cette classe en définissant les propriétés suivantes :

  1. source est une référence sur l'objet suivi ;
  2. valueKey est le nom de la propriété à suivre dans l'objet source ;
  3. value est la valeur de la propriété valueKey dans l'objet source.

Pour l'implémentation de ses propriétés nous allons utiliser là encore les fonctionnalités d'Objective-C 2.0 pour limiter le code à écrire.

Pour faciliter la création des objet une méthode de classe createValueTrackerForKey:onObject va également être utile pour créer un ValueTracker sur une propriété particulière d'un objet spécifique.

L'interface à implémenter est donc la suivante :

ValueTracker.h
Sélectionnez
@interface ValueTracker : NSObject {
@private
   id source;
   NSString * valueKey;
   id value;
}

@property(retain, readwrite) id source;
@property(assign, readwrite) id value;
@property(retain, readwrite) NSString * valueKey;

+ (ValueTracker *) createValueTrackerForKey: (NSString *) aKey onObject: (id) anObject;

IV-C. La classe ValueTracker, ajout de KVO

L'implémentation du modèle KVO implique deux étapes :

  1. La gestion de l'abonnement aux changements ;
  2. L'implémentation du suivi des modifications.

Pour la première partie nous allons définir une méthode pour activer l'abonnement et une autre pour supprimer l'abonnement. Leur signature est à ajouter dans le fichier d'interface :

ValueTracker.h
Sélectionnez

- (void) enableObserving;
- (void) disableObserving;

Pour l'implémentation elle suit les spécifications de l'interface.

ValueTracker.m
Sélectionnez

+ (ValueTracker *) createValueTrackerForKey: (NSString *) aKey
                                onObject: (id) anObject
{
   ValueTracker * tracker = [[ValueTracker alloc] init];
   
   tracker.valueKey = aKey;
   tracker.source = anObject;
   tracker.value = [anObject valueForKey: tracker.valueKey];
   
   return tracker;
}

- (void) enableObserving
{
   if ( nil != source ) {
       [source addObserver: self
                forKeyPath: valueKey
                   options:(NSKeyValueObservingOptionNew |
                            NSKeyValueObservingOptionOld)
                   context: NULL];
   }
}

- (void) disableObserving
{
   if ( nil != source ) {
       [source removeObserver:self];
   }
}

La seconde étape est de fournir une méthode appelée pour chaque modification. Son interface est définie dans l'interface et le fichier en-tête de notre classe peut simplement se contenter de le rappeler. Son implémentation est triviale :

ValueTracker.m
Sélectionnez
- (void) observeValueForKeyPath: (NSString *) keyPath
                      ofObject: (id) object
                        change: (NSDictionary *) change
                       context: (void *) context
{
   NSLog(@"Value %@ changed! Here is the changeset: %@", keyPath, change);
   value = [change valueForKey:NSKeyValueChangeNewKey];
}

IV-D. Tester KVO

Pour tester nos deux classes nous allons procéder par étapes :

  1. Création des objets ;
  2. Premier test en modifiant la position sans activer l'observation
  3. Second test en activant l'observation d'une propriété puis en effectuant des modifications.
  4. Troisième test en activant l'observation d'une autre propriété.

Le test est réalisé dans la méthode main() du fichier source créé par le modèle de projet.

Création des objets
Sélectionnez
Position* pos = [[Position alloc] init];
pos.latitude = 45.0;
pos.longitude = 12.0;
pos.altitude = 30.0;
   
ValueTracker * tracker = [ValueTracker createValueTrackerForKey:@"latitude" onObject:pos];
   
NSLog(@"Tracker (1): %@", tracker);

Si vous exécutez le projet dans cet état vous aurez les traces suivantes (4)

 
Sélectionnez

2008-04-28 13:22:26.805 SimpleKVO[799:10b] *** Exemple sans observation

2008-04-28 13:22:26.808 SimpleKVO[799:10b] Tracker (1): Tracking latitude=45
test sans observation
Sélectionnez
pos.latitude += 5.0;
pos.longitude += 5.0;
pos.altitude += 10.0;
   
NSLog(@"Tracker (2): %@", tracker);

Les traces devraient montrer que la valeur de la propriété n'a pas été mise à jour dans l'objet tracker.

 
Sélectionnez

2008-04-28 13:22:26.809 SimpleKVO[799:10b] Tracker (2): Tracking latitude=45
test avec observation
Sélectionnez
[tracker enableObserving];

pos.latitude += 3.0;
pos.altitude += 7.0;

En revanche, dès lors que l'observation est activée la méthode de notification est immédiatement appelée au moment d'une modification et les traces reflètent ce suivi des modifications :

 
Sélectionnez
2008-04-28 13:22:26.809 SimpleKVO[799:10b] *** Activation de l'observation

2008-04-28 13:22:26.811 SimpleKVO[799:10b] Value latitude changed! Here is the changeset: {
   kind = 1;
   new = 53;
   old = 50;
}

Il est possible d'aller plus loin en obligeant l'objet observateur de suivre une propriété même s'il ne l'a pas prévu initialement. Le programme de test peut ainsi forcer le suivi de la propriété d'altitude.

Test avec observation supplémentaire
Sélectionnez
[pos addObserver: tracker
         forKeyPath: @"altitude"
            options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
            context: NULL];
   
pos.latitude += 3.0;
pos.altitude += 2.0;
   
NSLog(@"Tracker (3): %@", tracker);

Les traces montrent alors que ce sont bien les deux propriétés latitude et altitude qui sont suivies par notre objet tracker

 
Sélectionnez
2008-04-28 13:22:26.812 SimpleKVO[799:10b] *** Activation de l'observation de l'altitude

2008-04-28 13:22:26.812 SimpleKVO[799:10b] Value latitude changed! Here is the changeset: {
   kind = 1;
   new = 56;
   old = 53;
}

2008-04-28 13:22:26.813 SimpleKVO[799:10b] Value altitude changed! Here is the changeset: {
   kind = 1;
   new = 49;
   old = 47;
}

2008-04-28 13:22:26.814 SimpleKVO[799:10b] Tracker (3): Tracking latitude=49

V. Un parallèle avec Java

Le mécanisme d'observation est très similaire à celui de gestion des évènements sur les Java Beans.

Cependant Java va un cran plus loin en proposant un modèle générique de gestion d'évènements.

Le seul modèle KVO correspond simplement à la gestion des évènements PropertyChange. Cependant là ou le modèle Cocoa est directement supporté au niveau de la classe de base, Java impose de définir :

  • une classe d'évènement PropertyChangeEvent;
  • une interface pour les observateurs PropertyChangeListener proposant une méthode propertyChange().
  • l'objet émetteur doit pour sa part fournir l'ensemble de outils techniques pour gérer la liste des observateurs de l'évènement en proposant des méthodes de gestion d'abonnement addPropertyChangeListener(), removePropertyChangeListener() ainsi que d'envoi de notification.

Si Java propose une architecture potentiellement plus riche et puissante elle est également beaucoup plus complexe à mettre en oeuvre et fait intervenir bien plus de classes.

VI. En Conclusion

Le modèle KVO propose un mécanisme simple et minimaliste pour qu'un objet puisse suivre les variations de valeur d'une propriété d'un autre objet.

C'est un atout indiscutable lorsqu'il est question de construire des interfaces graphiques où un seul objet modèle est susceptible d'impliquer la mise à jour de plusieurs composants graphiques. C'est ainsi que KVO permet la mise en oeuvre de la liaison de données entre modèle de donnée et présentation.

C'est également grâce au modèle KVO que Cocoa peut proposer le framework Core Data pour la persistance des données.

Les atouts combinés des modèles KVC et KVO permettent à Cocoa de proposer une large palette de services sans sacrifier à la simplicité des classes de bases.


POJO et non Enterprise Java Beans
Ce qui en français pourrait se traduire par Observation des paires de clef-valeurs.
Ce qui suppose que vous utilisez au minimum Léopard et les fonctionnalités de la version 2 du langage Objective-C.
Il est nécessaire d'afficher la console pour voir le résultat des NSLog en utilisant les touche Majuscule+Commande+R.

  

Licence Creative Commons
Le contenu de cet article est rédigé par Sylvain Gamel et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.