Follow me
RSS feed
My sources
My Viadeo

KVC-KVO avec MacRuby

Greg | 17 Mar 2010

Dev Le KVC (Key Value Coding) et le KVO (Key Value Observing) sont des modèles que vous risquez de rencontrer très rapidement si vous faites du développement Cocoa. Le premier est un système générique d'accès aux propriétés des objets, dont l'utilité, dans le cadre de MacRuby, ne vaut d'être connu que si vous utilisez le second. Le KVO quant à lui est un mécanisme qui vous permet d'observer les changements ayant lieu sur les valeurs des attributs d'un objet.

Avant de rentrer dans les détails, je tiens à vous signaler que, pour ceux qui voudraient rester dans du Ruby standard, il est possible de mettre en place ces mécanismes via le module KeyValue de Melchior Brislinger.

KVC

Le principe du KVC veut que chaque attribut d'un objet soit identifiable par un nom. Il est alors possible d'accéder à cet attribut via ce nom. Pour cela, il est nécessaire de respecter certaines contraintes lors de la création de la classe. Imaginons une classe Person pour laquelle nous avons les attributs firstName et lastName. En Ruby, nous codons généralement cela de la manière suivante :

class Person
  attr_accessor :firstname
  attr_accessor :lastName
end

La présence d'attr_accessor indique à Ruby qu'il doit mettre en place les accesseurs en lecture et écriture pour les attributs firstName et lastName. Ceci se vérifie effectivement :

p = Person.new
p.firstname = "Greg"
puts p.firstname
# => Greg

Pour être conforme au modèle KVC de Cocoa, la documentation nous indique que les accesseurs doivent avoir la forme suivante :

p.setFirstName("Greg")
p.firstName
# => "Greg"

En ce qui conserve la lecture, c'est déjà le cas. Bonne nouvelle, c'est également vrai pour l'écriture. En effet, si vous testez les 2 lignes de codes ci-dessus, vous verrez que cela fonctionne parfaitement avec MacRuby.

Nous pouvons donc dire que nous sommes conformes au modèle KVC. En effet, nous pouvons accéder de façon générique à nos attributs en utilisant les méthodes setValue:forKey: et valueForKey: :

p.setValue("Muriel", forKey:"firstName")
p.valueForKey("firstName")
# => "Muriel"

"C'est tout ?
- Oui, c'est aussi simple que cela !

KVO

Le KVO est presque aussi simple. En effet, il s'agit simplement de mettre en place un observer sur chaque attribut dont nous souhaitons contrôler les modifications de valeur. Pour cela nous utiliserons deux méthodes :

En plus de ces deux méthodes, nous devrons implémenter la méthode observeValueForKeyPath:ofObject:change:context: dans notre classe d'observation.

Si nous reprenons l'exemple de notre classe Person, nous pouvons par exemple, mettre en place un observer sur l'attribut firstName de la façon suivante :

observer = Observer.new
p.addObserver(Observer.new, forKeyPath:"firstName", options:0, context:nil)

Par la suite, si nous n'en avons plus besoin, nous pourrons supprimer cet observer :

p.removeObserver(observer)

Il ne nous reste plus qu'à créer la classe Observer. Comme je l'ai indiqué, la seule contrainte de cette classe est d'implémenter la méthode observeValueForKeyPath:ofObject:change:context:. Cette dernière reçoit en paramètre le nom de l'attribut observé, l'objet auquel il appartient, les changements observés et le contexte (celui passé à addObserver:forKeyPath:options:context:).

class Observer
  def observeValueForKeyPath(aKey, ofObject:anObject, change:ch, context:ctx)
    puts "La valeur de #{aKey} vient de changer dans l'object #{anObject.class}"
  end
end

Si nous voulons tester que tout cela fonctionne bien, il suffit de modifier la valeur de l'attribut firstName :

p.firstName = "Maia"
# => 
p.setFirstName("Colyne")
# => La valeur de firstName vient de changer dans l'object #<Class:0xXXXXXXXX>

Comme vous pouvez le voir, le KVO ne fonctionne pas si nous utilisons la syntaxe p.firstName = ..., il faut donc se contraindre à utiliser p.setFirstName( ... ) ou p.setValue(..., forKey:"firstName").

Avant de terminer, sachez que les informations de modifications (paramètre change:) que vous pouvez récupérer dans observeValueForKeyPath:ofObject:change:context: dépendant des options mises en place via addObserver:forKeyPath:options:context:. Il existe quatre valeurs possibles pouvant être combinées par un ou (|) :

Pour voir cela en action, modifier l'exemple de la façon suivante :

class Person
  attr_accessor :firstName
  attr_accessor :lastName
end

class Observer
  def observeValueForKeyPath(aKey, ofObject:anObject, change:ch, context:ctx)
    puts "La valeur de #{aKey} vient de changer dans l'object #{anObject.class}"
	p ch
  end
end

p = Person.new

p.setValue("Greg", forKey:"firstName")
puts p.valueForKey("firstName")

observer = Observer.new
opt = 0x01|0x02
p.addObserver(Observer.new, forKeyPath:"firstName", options:opt, context:nil)

p.setFirstName("Muriel")

Ceci donnera alors le retour suivant :

Greg
La valeur de firstName vient de changer dans l'object #<Class:0xXXXXXXXXX>
{"kind"=>1, "old"=>"Greg", "new"=>"Muriel"}
Copyright © 2009 - 2011 Grégoire Lejeune.
All documents licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 2.5 License, except ones with specified licence.
Powered by Jekyll.