Kapitel 2
Objekte, Nachrichten und die Laufzeit
In einer objektorientierten Sprache wie Objective-C sind Objekte die Grundbausteine der Programmierung, da sie die Mittel bereitstellen, um Daten zu speichern und zu verschieben. Die Nachrichtenübermittlung ist der Prozess, über den die Objekte miteinander kommunizieren, um Daten verschieben und Arbeit erledigen zu können. Um effizienten und wartungsfreundlichen Code schreiben zu können, ist ein genaues Verständnis dieser beiden Elemente erforderlich.
Die Laufzeitkomponente von Objective-C ist der Code, der als »Motor« der Sprache fungiert, wenn das Programm ausgeführt wird. Sie stellt die unverzichtbaren Funktionen für die Nachrichtenübermittlung zwischen den Objekten und die Logik bereit, die hinter dem Anlegen von Klasseninstanzen steht. Wenn Sie genau wissen, wie die einzelnen Elemente zusammenwirken, können Sie effektiver programmieren.
Thema 6: Grundlagen von Propertys
Propertys sind ein Merkmal von Objective-C, das die Kapselung der in einem Objekt enthaltenen Daten ermöglicht. Gewöhnlich enthalten Objekte in Objective-C einen Satz Instanzvariablen, in denen sie die für ihre Arbeit benötigten Daten speichern. Der Zugriff auf diese Instanzvariablen erfolgt normalerweise über Zugriffsmethoden. Mit einer Get-Methode wird die Variable gelesen, mit einer Set-Methode geschrieben. Dieses Konzept wurde standardisiert und in Form von Propertys in Objective-C 2.0 zur Verfügung gestellt. Mithilfe von Propertys kann der Entwickler den Compiler anweisen, automatisch Zugriffsmethoden zu schreiben. Dafür wurde eine neue »Punktschreibweise« eingeführt, um den Zugriff auf die in Klassen gespeicherten Daten kompakter zu machen. Wahrscheinlich haben Sie schon mit Propertys gearbeitet, aber möglicherweise kennen Sie noch nicht alle verfügbaren Optionen. Außerdem sind Ihnen vielleicht nicht alle Feinheiten bekannt. Thema 6 behandelt die Grundlagen von Propertys und stellt deren wichtigste Merkmale vor.
In einer Klasse zur Beschreibung einer Person können der Name der Person, das Geburtsdatum, die Adresse usw. gespeichert sein. Die Instanzvariablen können Sie wie folgt im öffentlichen Interface für die Klasse deklarieren:
@interface EOCPerson : NSObject {
@public
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end
Wenn Sie Kenntnisse in Java oder C++ haben, wird Ihnen das vertraut vorkommen. Dort können Sie den Gültigkeitsbereich von Instanzvariablen festlegen. In modernem Objective-C wird diese Technik jedoch nur selten verwendet. Das Problem bei dieser Vorgehensweise besteht darin, dass der Aufbau des Objekts zur Übersetzungszeit definiert wird. Bei jedem Zugriff auf die Variable _firstName legt der Compiler den Offset zu dem Speicherbereich fest, in dem das Objekt gespeichert ist. Das funktioniert aber nur so lange, bis Sie eine weitere Instanzvariable hinzufügen. Nehmen wir beispielsweise an, Sie ergänzen vor _firstName eine weitere Instanzvariable:
@interface EOCPerson : NSObject {
@public
NSDate *_dateOfBirth;
NSString *_firstName;
NSString *_lastName;
@private
NSString *_someInternalData;
}
@end
Der Offset, der zuvor auf _firstName zeigte, verweist nun auf _dateOfBirth. Jeder Code, der mit dem festgelegten Offset arbeitet, liest nun den falschen Wert. Zur Veranschaulichung sehen Sie in Abbildung 2–1 das Speicherlayout der Klasse vor und nach der Ergänzung um die Instanzvariable _dateOfBirth (wobei die Verwendung von 4-Byte-Zeigern angenommen wird).
Code, der den Offset zur Übersetzungszeit berechnet, versagt bei einer Änderung der Klassendefinition, sofern er dann nicht neu kompiliert wird. Beispielsweise kann es in einer Bibliothek Code geben, der eine alte Klassendefinition verwendet. Wenn er mit Code verlinkt wird, der eine neue Klassendefinition nutzt, kommt es zur Laufzeit zu Inkompatibilitäten. Dieses Problem wird in den verschiedenen Sprachen auf unterschiedliche Weise gelöst. In Objective-C besteht der Ansatz darin, aus Instanzvariablen besondere Variablen zu machen, die von Klassenobjekten festgehalten werden, in denen der Offset gespeichert ist. (Mehr über Klassenobjekte erfahren Sie in Thema 14.) Der Offset wird dann zur Laufzeit nachgeschlagen. Bei einer Änderung der Klassendefinition wird der gespeicherte Offset aktualisiert, sodass bei jedem Zugriff auf die Instanzvariable der korrekte Offset verwendet wird. Es ist sogar möglich, Instanzvariablen zur Laufzeit zu Klassen hinzuzufügen. Man spricht hier von einer stabilen Binärschnittstelle (Application Binary Interface, ABI). Eine ABI definiert unter anderem die Vereinbarungen dafür, wie Code generiert werden soll. Bei einer stabilen ABI können Instanzvariablen auch in einer Klassenerweiterungskategorie (siehe Thema 27) und in der Implementierung definiert werden. Es ist also nicht mehr erforderlich, alle Instanzvariablen im Interface zu deklarieren und damit interne Informationen über die Implementierung in der öffentlichen Schnittstelle preiszugeben.
Abb. 2–1 Aufbau der Klassendaten vor und nach der Ergänzung um eine weitere Instanzvariable
Zur Überwindung des Problems trägt auch bei, dass Sie dazu angeregt werden, Zugriffsmethoden zu verwenden, anstatt direkt auf die Instanzvariablen zuzugreifen. Hinter den Propertys stehen zwar nach wie vor die Instanzvariablen, aber sie bilden eine saubere Abstraktion. Zwar könnten Sie selbst Zugriffsmethoden schreiben, doch da die Zugriffsmethoden wie in Objective-C üblich einem strengen Benennungsmuster folgen, war es möglich, ein Sprachkonstrukt einzuführen, um Zugriffsmethoden automatisch erstellen zu lassen. Hier kommt die Syntax mit @property ins Spiel.
Propertys verwenden Sie in der Definition eines Objektinterface, um eine Standardmöglichkeit für den Zugriff auf die im Objekt gekapselten Daten bereitzustellen. In diesem Sinne können Sie sich Propertys also als eine Kurzschreibweise dafür vorstellen, dass es Zugriffsmethoden für eine Variable eines gegebenen Typs und Namens geben soll. Betrachten Sie beispielsweise die folgende Klasse:
@interface EOCPerson : NSObject
@property NSString *firstName;
@property NSString *lastName;
@end
Für einen Benutzer der Klasse ist das gleichbedeutend damit, die Klasse auf die folgende Weise zu schreiben:
@interface EOCPerson : NSObject
- (NSString*)firstName;
- (void)setFirstName:(NSString*)firstName;
- (NSString*)lastName;
- (void)setLastName:(NSString*)lastName;
@end
Um eine Property zu verwenden, setzen Sie die Punktschreibweise ein – ähnlich wie beim Zugriff auf ein Element in einem dem Stack zugewiesenen struct in C. Der Compiler wandelt die Punktschreibweise in Aufrufe der Zugriffsmethoden um, als hätten Sie sie direkt aufgerufen. Daher gibt es keinen Unterscheid zwischen der Punktschreibweise und dem direkten Aufruf der Zugriffsmethoden. Dies können Sie an dem folgenden Codebeispiel erkennen:
EOCPerson *aPerson = [Person new];
aPerson.firstName = @"Bob"; // Das Gleiche wie:
[aPerson setFirstName:@"Bob"];
NSString *lastName = aPerson.lastName; // Das Gleiche wie:
NSString *lastName = [aPerson lastName];
Das ist aber noch nicht alles, was es zu Propertys zu sagen gibt. Wenn Sie ihn nicht daran hindern, schreibt der Compiler automatisch den Code für die Zugriffsmethoden. Dieser Prozess wird automatische Synthese genannt. Beachten Sie, dass der Compiler dies zur Übersetzungszeit tut, sodass Sie im Editor keinen Quellcode für die synthetisierten Methoden sehen. Der Compiler schreibt aber nicht nur die Zugriffsmethode, sondern fügt der Klasse auch automatisch eine Instanzvariable des erforderlichen Typs hinzu, deren Name aus dem Namen der Property mit vorangestelltem Unterstrich besteht. Im vorhergehenden Beispiel werden also die beiden Instanzvariablen _firstName und _lastName erstellt. Mit dem Schlüsselwort @synthesize in der Implementierungsdatei der Klasse können Sie jedoch Einfluss auf die Benennung dieser Instanzvariablen nehmen:
@implementation EOCPerson
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end
Wenn Sie diese Syntax verwenden, erhalten Sie Instanzvariablen mit den Namen _myFirstName und _myLastName statt der Standardbezeichnungen. Es ist allerdings nicht üblich, Instanzvariablen einen anderen als den Standardnamen zu geben. Wenn Sie jedoch kein großer Freund von Unterstrichen bei der Benennung von Instanzvariablen sind, können Sie damit einen beliebigen Namen vergeben. Ich rate Ihnen jedoch dazu, beim Standardschema zu bleiben, da der Code dadurch besser für Personen verständlich ist, die denselben Konventionen folgen.
Wenn Sie nicht wollen, dass der Compiler die Zugriffsmethoden für Sie synthetisiert, können Sie sie auch selbst implementieren. Wenn Sie nur eine der Methoden schreiben, erstellt der Compiler jedoch nach wie vor die andere. Eine...