Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in /homepages/44/d199407414/htdocs/cocoading/Common/Stats.php on line 58

Warning: Cannot modify header information - headers already sent by (output started at /homepages/44/d199407414/htdocs/cocoading/Common/Stats.php:58) in /homepages/44/d199407414/htdocs/cocoading/Common/Stats.php on line 68

Warning: mysql_fetch_assoc(): supplied argument is not a valid MySQL result resource in /homepages/44/d199407414/htdocs/cocoading/Common/Article.php on line 57
[co coa:ding] — Binding-Proxys beseitigen

Binding-Proxys beseitigen

Description

Staubsaugervertreter klopfen an unsere Cocoa-Tür!

Nein, es geht nicht um Saugleistung, wenngleich es Stellen bei Cocoa gibt, die saugen. Es geht um Proxies. Ich will mich auch nicht darüber beschweren, da sie für ein gutes Laufzeitverhalten sorgen. Nur möchte man die wirklich manchmal loswerden.

Das häufigste Problem dürfte sein, dass man brav ein Binding gesetzt hat und nun dem selektierten Dingens aus dem Table-View eine Nachricht schicken möchte, damit es -sonstwas macht. Wer das probiert, landet in den ew‘gen Jagdgründen der Exceptions. Woran liegt das und wie bekommt man es hin?

Zuerst muss man verstehen, was ein Proxy ist: Ein Proxy ist eine von Cocoa hingestellte Instanz, die für das eigentlich verwiesene Originalobjekt steht. Wenn wir also etwa naiv das selektierte Objekt beim Controller abfragen wollen,

Lost In Code
    //  irgendwo zwischendrin
    MyClass* selection = [myController selection];

    //  Log: _NSControllerObjectProxy
    NSLog( @“the class is %@“, NSStringFromClass( [selectedObject class] );

so bekommen wir nicht dies, sondern die davor stehende Proxy-Instanz:

Dieser Proxy implementiert aber nicht den Methodensatz, den unser Ursprungsobjekt hat. Daher würde folgende Zeile eine Exception werfen, obwohl wir die Methode -doSomething in MyClass programmiert haben:

Lost In Code
    //  Loest eine Exception aus
    [selection doSomething];

Das liegt daran, dass der Proxy lediglich verspricht, die KVC-Methoden -valueForKey: und -setValue:forKey: zu implementieren.

Alle anderen Methoden – wie unser -doSomething – sind indessen verboten:

Der Trick besteht nun darin, dafür zu sorgen, dass wir vor dem Versand der Methode uns wieder das Original holen. Da es keine offizielle Methode des Proxies gibt, die das erledigt, müssen wir uns diesen selbst besorgen. Dazu müssen wir allerdings irgendwie den Proxy dazu überreden, überhaupt etwas mit dem Original anzufangen. Die Lösung kann also nur in einer KVC-Methode liegen. Um das ganze noch handlich zu machen, habe ich gleich daraus eine Kategorie gemacht:

ObjectUnproxyAddition.m
@implementation NSObject( ObjectUnproxyAddition ) 
- (id)unproxy { 

    //  Eine bekannte Proxy-Klasse
    if( [self isKindOfClass:[_NSControllerObjectProxy class]] ) {
        return [self valueForKey:@"unproxy"];

    //  Es gibt jede Menge. Man könnte auch auf „_NS*Proxy“ vergleichen
    } else if( … ) {
        …

   // Keine Proxy-Klasse gefunden. Es muss ein Original sein.
    } else {
        return self; 
    }
}
@end

Bleibt nur die Frage, wieso das funktioniert.

Erstens: Da es sich um eine Kategorie handelt, kennt jede von NSObject abgeleitete Klasse nunmehr die Instanzmethode -unproxy . Man kann diese also gefahrlos an die Selektion schicken:

Lost In Code
    //  -unproxy ist sogar fuer die private Klasse 
    //  _NSControllerObjectProxy bekannt
    MyClass* selection = [myController selection];
   selection = [selection unproxy];

Zweitens: Schicke ich diese Methode an die Proxy-Klasse, so wird der erste Teil (eine der  if -Zweige) ausgeführt:

ObjectUnproxyAddition.m
- (id)unproxy { 
   //  Eine bekannte Proxy-Klasse
    if( [self isKindOfClass:[_NSControllerObjectProxy class]] ) {
        return [self valueForKey:@"unproxy"];
   //  Es gibt jede Menge. Man könnte auch auf „_NS*Proxy“ vergleichen
    } else if( … ) {
        …
    //  we found no known proxy class. so it is the original object 
    } else {
        return self; 
    }
}

Dies bedeutet also, dass der Proxy nunmehr auf sich selbst eine KVC-Methode anwendet:

Drittens: Da – wie oben dargestellt – der Proxy die KVC-Nachrichten weiterleitet, landet diese Nachricht jetzt dort:

Viertens: Die Standardimplementierung von KVC versucht es jetzt einen entsprechenden Getter -unproxy zu finden. Sie wird auch fündig, da wir ja eine Kategorie hatten, die alle von NSObject abgeleiteten Klassen betrifft, womit -unproxy bei unserem Original vorhanden ist:

Es wird also automatisch im Original die Methode -unproxy ausgeführt. Da jetzt aber self auf das Original verweist, evaluiert [self class] nunmehr zur Originalklasse MyClass . Wir fallen also durch alle if -Zweife für die Proxies hindurch und landen im letzten else -Zweig der Methode:

ObjectUnproxyAddition.m
- (id)unproxy { 
   //  Eine bekannte Proxy-Klasse
    if( [self isKindOfClass:[_NSControllerObjectProxy class]] ) {
    return [self valueForKey:@"unproxy"];

    } else if( … ) {

    } else {
        return self; 
    }
}

Fünftens: Dieser Zweig gibt jedoch einen Zeiger auf sich selbst, also auf das Original zurück. Nachdem die Methode verlassen ist, befinden wir uns wieder im ersten Teil. (Daher kamen wir ja.) Dieser liefert nun das Ergebnis unverändert dem Aufrufer zurück, also immer noch den Zeiger auf das Original. Das Ergebnis: Wir haben einen Zeiger auf das Originalobjekt! Und dem können wir dann auch unser -doSomething schicken:

Lost In Code
   //  irgendwo zwischendrin
    MyClass* selection = [myController selection];

    //  das Original holen
    selection = [selection unproxy];

    //  do, what you want tina to do:
    [selection doSomething];

Et voilá!


Warning: mysql_num_rows(): supplied argument is not a valid MySQL result resource in /homepages/44/d199407414/htdocs/cocoading/Common/Article.php on line 206