[E] Objective-C: Zugriff auf Klassenelemente - Key-Value Observing
-
2 Zugriff auf Klassenelemente
Vorwort
C++ ist nicht der einzige Ansatz gewesen, um der Sprache C die Möglichkeiten objektorientierter Programmierung einzuhauchen. Es gab die Entwicklung von Objective-C, die ebenfalls diese Möglichkeiten ergänzte, und gleichzeitig eine Kompatibilität zu C erhielt. Die Sprache war in der Breite wenig bekannt, da sie vor allem auf den Mac-Systemen zur Applikationsentwicklung benutzt wurde und wird. Durch den Boom von Mac, iPhone und wohl bald auch iPad wird die Sprache allerdings zusehends wichtiger werden, daher gibt es im Magazin nun eine einführende Artikelserie zu Objective-C (kurz: ObjC).
Dieser Artikel ist von http://www.cocoa-coding.de übernommen und behandelt wie Klassen und Objekte in Objective-C aufgebaut sind.
2.3 Key-Value Observing
Ebenfalls ein sehr nützliches Verfahren ist Key-Value Observing, dass Sie zum Beispiel verwenden können, um Änderungen an einem Wert zu verfolgen oder zu protokollieren.
Realisiert wird das Ganze durch Observer, von denen Sie durchaus mehrere benutzen können.Gegeben sein soll wieder die Klasse
person
, die Ihnen noch aus dem letzten Kapitel bekannt ist. Ziel ist es Veränderungen an der Instanzvariablenname
zu überwachen.Person *myPerson = [[Person alloc] init]; [myPerson addObserver:myPerson forKeyPath:@"name"options:0 context:NULL];
Mit dem Befehl
addObserver
wird dem ObjektmyPerson
ein Observer zugewiesen, oder besser gesagt, angehängt. Das Wort add lässt schon darauf schliessen, dass es mehrere sein können. Als nächster Parameter folgt das Objekt, in dem die Methode des Observers zu finden ist. Dieses Objekt ist es, das bei einer Änderung benachrichtigt wird, dafür muss es eine genau definierte Methode implementieren. Dann gibt es den ParameterforKeyPath
welcher die Variable bestimmt.In diesem Beispiel liegt die Methode zur Überwachung auch in der Klasse person. Das muss nicht so sein, ist aber hier sehr praktisch. Fügen Sie also
Person.m
folgenden Methode hinzu.-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context { NSLog(@"name geändert"); }
In Ihrem Programm ändert sich der Name zwei Mal. Ein Mal durch
setName
und das zweite Mal durch Key-Value Coding. Der Observer sollte also diese Änderungen bemerken und dementsprechend Ausgaben machen.[myPerson setAlter:[NSNumber numberWithInt:32] ]; [myPerson setName:@"Müller"]; [myPerson setVorname:@"Max"]; NSString *wert = @"name"; [myPerson setValue:@"Huber"forKey:wert];
Sie können den gleichen Observer auch für eine weitere Variable benutzen.
Person *myPerson = [[Person alloc] init]; [myPerson addObserver:myPerson forKeyPath:@"name"options:0 context:NULL]; [myPerson addObserver:myPerson forKeyPath:@"vorname"options:0 context:NULL];
In diesem Fall müssen Sie aber unterscheiden, durch welchen Aufruf die Methode ausgelöst wurde. Durch den Parameter
keyPath
ist dies aber sehr leicht möglich.-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context { if( [keyPath isEqualToString:@"name"] ) { NSLog(@"name geändert"); } else if( [keyPath isEqualToString:@"vorname"] ) { NSLog(@"vorname geändert"); } }
Zwei weitere Parameter sind
change
undoptions
, mit denen Sie verfolgen können, wie sich ein Wert geändert hat. Dabei istchange
einNSDictionary
, eine Art Liste, welche die neuen und die alte Werte beinhaltet. Zuerst müssen sie allerdings bestimmen, welche Werte sie bekommen wollen. In diesem Beispiel natürlich den neuen und den alten Wert. Dies wird über den Parameteroptions
geregelt. Ändern Sie 0 zu(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
.Für den Rest des Beispiels begnügen wir uns mit der Variablen
name
. Diemain
-Methode kann daher etwas aufgeräumt werden und sollte etwa so aussehen:int main (intargc, const char* argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Person *myPerson = [[Person alloc] init]; [myPerson addObserver:myPerson forKeyPath:@"name" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL]; [myPerson setName:@"Müller"]; NSString *wert = @"name"; [myPerson setValue:@"Huber"forKey:wert]; NSLog(@"Name: %@",[myPerson valueForKey:wert]); [myPerson release]; [pool drain]; return 0; }
Wie zuvor wird
name
zwei mal geändert. Wie also muss die Observer-Methode angepasst werden, um diese Veränderungen zu protokollieren?if( [keyPath isEqualToString:@"name"] ) { NSLog(@"name geändert"); NSString *alterWert = [change valueForKey:NSKeyValueChangeOldKey]; NSLog(@"alter Wert=%@",alterWert); NSString *neuerWert = [change valueForKey:NSKeyValueChangeNewKey]; NSLog(@"neuer Wert=%@",neuerWert); }
Durch
NSKeyValueChangeOldKey
undNSKeyValueChangeNewKey
bekommt man die entsprechenden Einträge aus demchange
-Dictionary. Da Sie sicher sein können, dassname
einemNSString
entspricht, können Sie die Werte auch wieder in solch ein Objekt umwandeln.Gut erkennbar ist, dass
name
zuerst leer, also null, ist. Dann nimmt er den Text Müller an. Danach wird Müller mit Huber ersetzt und dieser Name zum Schluss ausgegeben.Am Anfang diese Kapitels haben Sie erfahren wie man einem Key einen Observer hinzufügt. Natürlich können Sie Observer auch wieder entfernen falls Sie das wünschen.
[myPerson removeObserver:myPerson forKeyPath:@"name"];
Anmerkung für Experten:
Die Methode [/c]observeValueForKeyPath
kommt ohne eine Definition in der Headerdatei aus, wie es auch bei
delegate-Methoden der Fall ist. Obwohl es ähnlich funktioniert, ist es aber keine solche Methode. Delegates werden aufgerufen wenn sie implementiert sind, sind sie das nicht, funktioniert das Programm weiterhin normal. Das ist bei
observeValueForKeyPath[c] nicht der Fall. Weisen Sie eine Klasse als Observer zu, die diese Methode nicht implementiert, wird das Programm zur Laufzeit abstürzen.