Lauf, Forest, lauf!

Wo habe ich gleich noch …?

Aufruf an Objektzeiger

Die dynamisch Suche zur Laufzeit nach einer Methode entlang der Vererbungshierarchie, um eine empfangene Nachricht zu verarbeiten, wird jeder Objective-C-Entwickler verinnerlicht haben. Spätestens nachdem er das ▹ Objective-C-Dokument von Apple gelesen hat, in welchem dies wunderbar erläutert ist.

Dennoch schauen wir uns das kurz an und bringen das Ganze in Zusammenhang mit den Elementen der objc_class-Struktur. Schließlich existiert diese Datenstruktur gerade um diese dynamische Suche zu ermöglichen.

Die Laufzeitumgebung besitzt also die Funktion objc_msgSend(). (Anmerkung: In Wirklichkeit gibt es mehrere Funktionen - je nach Rückgabewert- bzw. Argumenttypen. Deren Verhalten ist jedoch analog zu der hier beschriebenen Funktion.) Aus einem Methoden Aufruf wie [receiver message] erzeugt der Compiler Binärcode der  objc_msgSend( receiver, @selector(message) ) entspricht. Diese Funktion sucht die Methode, die dem übergebenen Selektor entspricht. Hierzu muss als erstes die Klasse des Objekts, das die Nachricht empfängt, ermittelt werden. Das ist einfach: Es muss einfach das isa-Element der objc_object Datenstruktur dereferenziert werden. Schon hat die Laufzeitumgebung Zugriff auf das Klassenobjekt (objc_class-Datenstruktur). Jetzt kann zuerst in derem Cache nach dem Selektor gesucht werden. Existiert die Methode dort noch nicht, wird das Element methodLists nach dem Selektor durchsucht. Wurde dieser nicht gefunden, wird die Suche in der Elternklasse (durch derefernzieren des super_class-Elements) fortgesetzt - und so weiter bis zur Wurzelklasse. Wurde die objc_method-Stuktur mit dem korrekten Selektor gefunden, kann die Funktion, die diese Methode implementiert, aufgerufen werden. Dies geschieht mit Hilfe des Funktionszeigers (method_imp-Elements der objc_method-Struktur). Außerdem wird ein Zeiger auf die entsprechende objc_method-Struktur im Cache gespeichert. Der Typ des Funktionszeiger ist wie folgt deklariert:

typedef id (*IMP)(id self, SEL _cmd, ...);

Die ersten beiden Parameter, der durch den IMP referenzierten Funktion, sind der Empfänger der Nachricht und der Selektor – also genau die ersten zwei Parameter die auch objc_msgSend() übergeben wurden. Der erste Parameter entspricht self, welches innerhalb der entsprechenden Objective-C-Methode verwendet werden kann. Der zweite Parameter ist der Selektor der Objective-C-Methode, auf den innerhalb der Methode _cmd zugegriffen werden kann.

Aufruf an super

Die Funktion objc_msgSendSuper() funktionert genau wie objc_msgSend(), nur dass die Suche nach einer Methode in der entsprechenden Elternklasse begonnen wird. Der Objective-C-Compiler generiert einen objc_msgSendSuper()-Aufruf wenn er auf einen Methodenaufruf mit dem super Schlüsselwort trifft, wie z.B. [super message]. (Anmerkung: Diese Funktion existiert wieder in ähnlicher Form für Methoden mit einer Datenstruktur als Rückgabewert. Die hier gezeigte ist für einfache Rückgabewerte.)

super entspricht nicht der Elternklasse des Empfängers der Nachricht, sondern der Elternklasse der Klasse in der der super-Aufruf erfolgt. Hierzu erzeugt der Compiler eine entsprechende objc_super-Datenstruktur sobald er auf das Schlüsselwort super trifft, um festzulegen welche Elternklasse gemeint ist. Die Klasse von super steht also bereits zur Compilierzeit fest. Das muss so sein, da sonst zur Laufzeit eine Endlosschleife entstehen könnte:

B.m
@implementation B
- (void) foo { [super foo]; }
@end
C.m
@interface C : B {
    … }
…
@end
LostInCode.m
C *c = [[C alloc] init]; // C erbt von der Klasse B, überschreibt
                         // allerdings nicht foo
[c foo];                 // es wird foo von B ausgeführt

C ist eine Unterklasse von B und überschreibt foo nicht. Darum wird die Version der Klasse B ausgeführt. Würde super dynamisch zur Laufzeit ermittelt werden, wäre super in foo in diesem Fall wieder die Klasse B.

objc_super ist folgendermaßen deklariert:

objc.h
struct objc_super { 
    id receiver; 
    Class class; 
};

receiver spezifiziert eine Instanz einer Klasse. class ist ein Zeiger auf eine objc_class-Struktur, welches das Klassenobjekt der Elternklasse ist, der die Nachricht gesendet werden soll.