Objective C: KVC und KVO

Ich empfinde das Eintauchen in die Welt von Objective C und Cocoa als sehr erfrischend, jeden Tag gibt es etwas neues zu entdecken. Wenn man sich mit anderen Entwicklern, die Objective C nicht kennen, unterhält, kommt aus der C++ Ecke garantiert die Frage: „Kann man dort Operatoren überladen?„.

Nein kann man nicht. Braucht man auch gar nicht. Operatoren zu überladen ist zweifelsohne cool, aber ich multipliziere nicht den ganzen Tag Matrizen, sondern baue Dinge für Endanwender. Das angenehme an Objective C ist, dass es bestimmte Real World Probleme sehr schön löst.

Key Value Observing ist so eine Lösung und es ist ein so verdammt einfaches Konzept, dass ich mich wundere, wie die Welt bisher ohne leben konnte… (Um es vorweg zu nehmen: GObject hat ein ähnliches Konzept und es gibt bestimmt noch mehr Implementierungen dieses Patterns).

Das Real World Problem kommt bestimmt jedem bekannt vor: man hat eine Klasse A gebaut und eine andere Klasse B muss benachrichtigt werden, wenn sich in A irgendetwas interessantes ändert. Bisher wäre das Herangehen, dass A eine Referenz auf B mitschleifen muss, damit Klasse A die Klasse B benachrichtigen kann. Das bringt natürlich das schöne Klassendesign durcheinander, eigentlich hat A mit B nichts zu tun und die Referenz auf B zu handhaben ist auch nervig.

In Objective C gibt es Properties, die Membervariablen und deren Getter und Setter erzeugen können:

// foo.h
@interface Foo
@property(nonatomic, copy) NSString* bar;
@end
 
// foo.m
@implementation Foo 
@synthesize bar;
@end

Im obrigen Beispiel hat die Klasse „Foo“ ein Member „bar“ und @synthesize generiert die Getter und Setter, die dann so verwendet werden können:

Foo *fooInstance = [Foo new]; // oder [[Foo alloc] init]
fooInstance.bar = @"Value";
NSLog(@"foo.bar=%@", fooInstance.bar); // "foo.bar=Value"
 
[fooInstance setBar:@"eulaV"]; // geht auch
NSLog("@foo.bar=%@", [fooInstance bar]); // "foo.bar=eulaV"
 
[fooInstance release];

Soweit, so trivial. Mit Key Value Coding kann man auf Properties programmatisch zugreifen. Das bedeutet, ich baue mir einen String zusammen, dessen Inhalt der Name einer Property ist und ich kann auf die Property zugreifen:

NSString* dings = [fooInstance valueForKey:@"bar"]; // "getter"
[fooInstance setValue:@"Hi there!" forKey:@"bar"]; // "setter"

Das wird unheimlich praktisch, wenn man z.B. XML parsen muss und die geparsten Key/Value-Paare direkt via KVC in den Properties einer Klasse speichern kann.

Key Value Observing ist nun die Lösung o.g. Problems: man kann sich einfach von außen als Observer für bestimmte Properties einer Klasse registrieren und wird benachrichtigt, falls sich ein Wert ändert.

Das spart eine Menge Code und die beobachtete Klasse A braucht sich um Klasse B nicht kümmern und muss keine Referenz auf B mitschleifen.

// als Observer registrieren ist einfach:
[fooInstance addObserver:self   // sag mir bescheid,
              forKeyPath:@"bar" // wenn sich bar ändert
               options:NSKeyValueObservingOptionNew
               context:NULL];
 
// der Observer muss diese methode implementieren, 
// um benachrichtigt zu werden:
- (void) observeValueForKeyPath:(NSString *)keyPath
		     ofObject:(id)object
		       change:(NSDictionary *)change
		      context:(void *)context 
{
    if ([keyPath isEqualToString:@"bar"])
        NSLog(@"new value for bar: %@", object);
}
 
// wenn man genug hat, meldet man sich wieder ab
[fooinstance removeObserver:self forKeyPath:@"bar"];

Ich finde das ziemlich elegant. Das Klassendesign bleibt sauber und trotzdem ist es möglich, sich kreuz und quer durch alle Schichten der Anwendung über Zustandsänderungen benachrichtigen zu lassen.

Update: Es gibt einen sehr schönen Wrapper von Andy Matuschak: Block Callbacks for Cocoa Observers (via Ole Bergmanns hoch interessantem Twitterstream)