Sie sind auf Seite 1von 224

O’Reillys Taschenbibliothek

Objective-C

kurz & gut

OREILLY

Lars Schulten

Objective-C

kurz & gut

Lars Schulten

Objective-C kurz & gut Lars Schulten Beijing Cambridge Farnham Köln Sebastopol Tokyo

Beijing Cambridge Farnham Köln Sebastopol Tokyo

Die Informationen in diesem Buch wurden mit größter Sorgfalt erarbeitet. Dennoch können Fehler nicht vollständig ausgeschlossen werden. Verlag, Autoren und Übersetzer übernehmen keine juristische Verantwortung oder irgendeine Haftung für eventuell verbliebene Fehler und deren Folgen. Alle Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt und sind möglicherweise eingetragene Warenzeichen. Der Verlag richtet sich im Wesentlichen nach den Schreibweisen der Hersteller. Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

Kommentare und Fragen können Sie gerne an uns richten:

O’Reilly Verlag GmbH & Co. KG Balthasarstr. 81 50670 Köln E-Mail: kommentar@oreilly.de

Copyright der deutschen Ausgabe:

2013 O’Reilly Verlag GmbH & Co. KG 1. Auflage 2013

Die Darstellung eines Steppenfuchses im Zusammenhang mit dem Thema Objective-C ist ein Warenzeichen von O’Reilly Media, Inc.

Bibliografische Information Der Deutschen Nationalbibliothek Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar.

Lektorat: Alexandra Follenius, Köln Fachgutachten: Ingo Dellwig, Hannover Korrektorat: Sibylle Feldmann, Düsseldorf Produktion: Andrea Miß, Köln Umschlaggestaltung: Ellie Volckhausen, Sebastopol & Michael Oreal, Köln Satz: Reemers Publishing Services GmbH, Krefeld, www.reemers.de Druck: fgb freiburger graphische betriebe; www.fgb.de

ISBN: 978-3-86899-373-8

Dieses Buch ist auf 100% chlorfrei gebleichtem Papier gedruckt.

Inhalt

Einfu¨ hrung

1

1 Grundlagen

5

Ein erstes Beispiel

5

Code schreiben und kompilieren

6

Headerdateien

8

Frameworks

9

2 Syntax

11

Klassen und Objekte

11

Nachrichten

12

Compilerdirektiven

13

Literaldeklarationen

14

Blocks

18

Ein zweites Beispiel

19

3 Objekte

23

Objektvariablen und Zeiger

23

Objekte erstellen

27

Mit Objekten interagieren

28

Objekte vergleichen

29

Objekt-Enumeration

30

Objektindizierung

30

4 Klassen

33

Klassendeklaration und -definition

33

Felder

35

Methoden

38

Eigenschaften

47

Vererbung und Polymorphie

56

Typumwandlungen

61

5 Kategorien und Klassenerweiterungen

63

Klassenerweiterungen

65

6 Protokolle

67

Ein Protokoll deklarieren

67

Protokolle implementieren

69

Optionale Methoden aufrufen

70

Protokolle als Referenztypen

71

Informelle Protokolle

71

7 Fehler und Ausnahmen

73

Fehler

74

NSError-Referenz

76

Ausnahmen

78

NSException-Referenz

83

8 NSObject

85

9 Objektlebenszyklus

87

 

Objekte erstellen

88

Objekte kopieren

95

Objektvernichtung

99

10

Speicherverwaltung

101

Was Speicherverwaltung ist

101

Speicherverwaltungstechnologien unter Objective-C

102

ARC (Automatic Reference Counting)

104

MRC (Manuel Reference Counting)

110

11 Laufzeitinformationen

115

Objektinformationen

116

Klasseninformationen

117

Funktionsinformationen

123

Protokollinformationen

124

12 Messaging

127

Kompilierung

128

Selektoren

129

Implementierungen

130

Selektoren dynamisch ausfu¨hren

131

Implementierungen cachen

135

Dynamische Methodenimplementierung

137

Nachrichtenweiterleitung

139

13 Key/ Value-Coding

143

KVC-Konformita¨ t

143

Schlu¨sselbasierte Interaktion mit Objekten

144

Schlu¨sselpfade

145

Virtuelle Schlu¨ssel

146

KVC-Validierung

147

Schlu¨sselpfadoperatoren

148

14 Objektarchivierung

151

Sequenzielle und schlu¨sselbasierte Archive

153

Die NSCoding-Methoden implementieren

153

Coder-Objekte

156

15 Blocks

159

Blocks definieren

159

Blocks

als Objekte

161

Zugriff auf den Kontext

161

Lokale Variablen

165

Globale und kontextgebundene Blocks

168

16 NSObject-Referenz

173

17 Compilerdirektiven

185

18 Alternative Plattformen

195

Compiler

196

Clang/LLVM

196

Laufzeitumgebungen und Plattformen

197

19 Clang-Optionen

201

Index

205

Einfu¨ hrung

Objective-C ist die Programmiersprache der letzten Jahre. Die Be- liebtheit von Apples Plattformen, iOS und Mac OS X, sorgt dafür, dass die Sprache heute aktueller ist als je zuvor. Objective-C – kurz & gut ist ein kompaktes Handbuch zur Programmiersprache Objective-C. Es illustriert die grundlegenden Aspekte der Sprache mit kurzen Erklärungen, die von kleineren Beispielen aufgelockert werden, und bietet einen Überblick über die wichtigsten Technolo- gien, Klassen, Nachrichten und Funktionen. Es richtet sich an alle, die bereits für andere Plattformen programmiert haben und sich nun in diese spannende Sprache einarbeiten wollen.

und sich nun in diese spannende Sprache einarbeiten wollen. Sie sollten mit mindestens einer Sprache ver-

Sie sollten mit mindestens einer Sprache ver- traut sein, die eine C-artige Syntax nutzt. Ob- jective-C setzt auf C auf und nutzt viele Ele- mente seiner Syntax unverändert. Das heißt, elementare Syntaxformen, wie Schleifen und Bedingungsanweisungen, sowie die elementa- ren C-Datentypen werden in diesem Buch nicht erläutert. Das bedeutet aber nicht, dass Sie C selbst beherrschen müssen. Eine belie- bige an C angelehnte Programmiersprache mit äquivalenten Sprachelementen, Java oder C# beispielsweise, reicht vollkommen aus. Wenn Sie bislang nur mit dynamisch typisierten Spra- chen wie z. B. JavaScript gearbeitet haben, sollten Sie sich allerdings unbedingt mit stati- schen Typsystemen vertraut machen.

Objective-C

Objective-C ist ein C-Dialekt. Es bildet eine vollständige Ober- menge zu C und bietet alles, was auch C bietet. Es besitzt die gleichen Syntaxstrukturen und -elemente, nutzt die gleichen Daten- typen und kann sogar auf die gleichen Bibliotheken zugreifen. Wenn Sie sich Objective-C von C her nähern, werden Ihnen also viele Dinge bekannt vorkommen. Sie könnten einem Objective- C-Compiler sogar Ihren alten C-Quellcode vorsetzen, und dieser würde ihn anstandslos kompilieren.

Haben Sie keine Erfahrung mit C, müssen Sie sich zusätzlich zum eigentlichen Objective-C-Kern noch mit gewissen Aspekten von C auseinandersetzen, die bei der Arbeit mit Objective-C unumgäng- lich sind. Auf einige dieser Aspekte wie C-Zeiger werden wir (kurz) eingehen, da ansonsten ein Verständnis von Objective-C unmöglich ist. Andere wie die diversen C-Datentypen und C-Syntaxstrukturen werden wir hier nicht beschreiben, da wir davon ausgehen, dass Sie bereits mit Sprachen wie Java, C# usw. gearbeitet haben, die ähnliche Features bieten. Sollte das nicht der Fall sein, müssen Sie die entsprechenden Informationen in einem geeigneten C-Buch nachschlagen.

Objective-C ist aber auch eine objektorientierte Programmierspra- che, die alle Features bietet, die Ihnen aus anderen objektorientier- ten Programmiersprachen vertraut sein sollten: Objekte, Klassen, Schnittstellen, Vererbung, Polymorphie usw. Dazu erweitert es C um die entsprechenden Strukturen und Syntaxformen: Compiler- direktiven, Syntaxstrukturen zur Definition von Klassen und ihren Elementen, Syntaxstrukturen zur Arbeit mit Klassen und ihren Elementen und einige neue Datentypen. Das Buch erläutert die wichtigsten Aspekte der objektorientierten Programmierung und ihre spezifische Implementierung in Objective-C und geht dabei auch explizit auf die Punkte ein, an denen sich die objektorientierte Programmierung unter Objective-C von der in z. B. Java unterschei- det.

Werkzeuge und Plattformen

Objective-C ist eigentlich plattformunabhängig. Es gibt eine Reihe von Compilern, die Objective-C-Code kompilieren können, und verschiedene Laufzeitumgebungen, die in Objective-C-Code einge- bunden werden können. In diesem Buch werden wir uns auf die Werkzeuge und die Laufzeitumgebung für Apples Plattformen kon- zentrieren. Einen kurzen Überblick über andere verfügbare Werk- zeuge und Bibliotheken finden Sie in Kapitel 18, Alternative Platt- formen. Da Apple die Sprache in den letzten Jahren umfassend erweitert hat, kann es sein, dass diese die neuesten Sprach-Features nicht immer vollständig unterstützen.

Sprach-Features nicht immer vollständig unterstützen. Wir werden hier Objective-C in der Version behandeln, in der

Wir werden hier Objective-C in der Version behandeln, in der es von den Werkzeug- und Betriebssystemversionen unterstützt wird, die aktuell waren, als dieses Buch geschrieben wurde: XCode 4.6 mit Clang 4.2 und LLVM 3.2 sowie Mac OS X 10.8 und iOS 6. Das heißt auch, dass als Entwicklungsplattform ein Sys- tem erforderlich ist, auf dem mindestens OS X 10.7 (Lion) läuft, da die benötigten Versionen von Xcode und Clang für frühere Betriebssys- temversionen nicht verfügbar sind.

Die Beispiele

Die Beispiele sind eher knapp und nicht vollständig. In der Regel dienen sie nur der Illustration eines einzigen Problems und sind deshalb so einfach wie möglich gehalten. Sie machen extensiven Gebrauch von den neuesten Objective-C-Technologien und gehen, soweit nicht anders vermerkt, davon aus, dass der Code mit ARC- Unterstützung kompiliert wird.

KAPITEL 1

Grundlagen

Objective-C bildet eine vollständige Obermenge zu C. Es nutzt nicht nur die gleichen elementaren Syntaxstrukturen, Datentypen und Schlüsselwörter wie C, sondern die Programmierung in Ob- jective-C weist auch die gleichen Grundstrukturen wie die Program- mierung in C auf.

Ein erstes Beispiel

Ein primitives Objective-C-Programm sieht fast genau so aus wie ein äquivalentes C-Programm und weist nur unerhebliche Unter- schiede zu einem äquivalenten Java- oder C#-Programm auf:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[])

{

 

@autoreleasepool { NSLog(@"Die Welt ist alles, was der Fall ist!");

}

return 0;

}

Folgende Elemente sollten Sie aus C oder einer von C abgeleiteten Sprache kennen:

• Die main()-Funktion dient als »Einsprungpunkt«. Die Ausfüh- rung jedes Objective-C-Programms beginnt mit dieser Funk- tion. Beachten Sie, dass main() eine klassische Funktion ist, keine Methode wie in Java oder C#.

• Die Elemente, mit denen der Code operiert (Funktionen, Variablen usw.), haben stets einen Typ (int main(), int argc usw.).

• Anweisungsblöcke stehen in geschweiften Klammern.

• Anweisungen werden mit einem Semikolon abgeschlossen.

Unbekannt könnten Ihnen die #import-, @autorealesepool- und @"…"-Konstruktionen sein, da das Objective-C-eigene Syntaxele- mente sind:

#import ist eine Objective-C-spezifische Präprozessordirektive, die eine Headerdatei importiert.

@autoreleasepool ist eine Objective-C-Compilerdirektive.

@"…" definiert ein Objective-C-Stringliteral.

Die beiden @-Formen werden in Kapitel 2, Syntax, ausführlicher erläutert, #import im Abschnitt »Headerdateien« auf Seite 8.

NSLog() ist eine Protokollierungsfunktion, die Nachrichten mit einem Zeitstempel versehen auf der Konsole ausgibt. Als erstes Argument erwartet sie ein Objective-C-Stringliteral. Das Stringlite- ral kann Formatangaben enthalten, die durch die nachfolgenden Argumente ersetzt werden, zum Beispiel:

NSLog(@"Das Leben ist %@", 1>0? @"scho¨n" : @"be…");

Die Formatangabe %@ formatiert ein Objective-C-Objekt, hier das Objective-C-Stringobjekt, zu dem der Bedingungsausdruck aus- gewertet wird. Daneben unterstützt Objective-C die von der printf- Spezifikation definierten Formatzeichen.

Code schreiben und kompilieren

Unter Mac OS X haben Sie zwei Möglichkeiten, Objective-C-Code zu schreiben und zu erstellen. Sie können beides mit Apples IDE Xcode tun, oder Sie schreiben mit einem Programm Ihrer Wahl und erstellen mit Apples Kommandozeilenwerkzeugen.

Xcode erhalten Sie im Mac App Store oder auf Apples Entwick- lerseiten unter https://developer.apple.com/downloads/. Die Kom- mandozeilenwerkzeuge können Sie aus Xcode (unter

Preferences…/Downloads/Components/Command Line Tools) instal- lieren oder separat von Apples Entwicklerseiten für Ihr System herunterladen. Beachten Sie, dass Downloads und viele andere Ressourcen nur Mitgliedern in Apples Entwicklerprogramm zu- gänglich sind. Eine kostenlose Mitgliedschaft genügt.

Xcode

Der Code in diesem Beispiel ist eine nur minimal angepasste Fassung des Inhalts der main.m-Datei, die die Xcode-Projektvorlage für ein Kommandozeilenprojekt generiert. Sie können diesen Code kompilieren und ausführen, indem Sie in Xcode ein neues Kom- mandozeilenprojekt anlegen (File/New/Project/OS X/Application/ Command Line Tool), dann nach Belieben vor dem Ausführen links im Projektnavigator main.m anwählen und im Editor die Änderun- gen vornehmen. Oder Sie klicken gleich auf den Run-Button, den Sie in Abbildung Abbildung 1-1 oben links sehen, um die Kom- pilation und Ausführung des Programms anzustoßen.

die Kom- pilation und Ausführung des Programms anzustoßen. Abbildung 1-1: Apples IDE, Xcode Code schreiben und

Abbildung 1-1: Apples IDE, Xcode

Kommandozeilenkompilation

Alternativ können Sie die Datei über die Kommandozeile kompilie- ren und ausführen. Geben Sie den Code in einen Texteditor Ihrer Wahl ein und speichern Sie die Datei unter einem Namen, der die Dateinamenserweiterung .m trägt, z.B. welt.m. Das ist die übliche Dateinamenserweiterung für Objective-C-Dateien. Öffnen Sie die Terminalanwendung und navigieren Sie in das Verzeichnis, in dem Sie Ihre Datei gespeichert haben. Geben Sie dann den folgenden Befehl ein (vorausgesetzt, Sie haben den gleichen Namen gewählt wie wir), um den Code zu kompilieren:

clang -fobjc-arc -framework Foundation -o Welt welt.m

clang ist Apples »Objective-C-Compiler«. Der Kommandozeilenschal- ter -fobjc-arc fordert Automatic Reference Counting (siehe Kapitel 10, Speicherverwaltung) an, -framework gibt die Frameworks an, gegen die gelinkt werden soll, -o den Namen der Ausgabedatei. Das Kompilat können Sie einfach mit folgender Anweisung ausführen:

./Welt

Headerdateien

Header enthalten Deklarationen (von Klassen und Funktionen bei- spielsweise) und Definitionen (von Typen und Makros). Es sind echte physische Dateien, die üblicherweise die Dateinamenserwei- terung .h tragen. Sie werden in Codedateien eingelesen, um die in ihnen enthaltenen Deklarationen und Definitionen in diesem Code verfügbar zu machen. Das Codebeispiel importiert den Header Foundation/Foundation.h, damit der Compiler weiß, wie er mit den beiden @…-Konstrukten umzugehen hat.

Eigene Headerdateien schreiben Sie bei Objective-C in der Regel, um Klassen und ihre Schnittstellen zu deklarieren (diesen Aspekt werden wir uns im Kapitel 4, Klassen, ansehen).

Header importieren

#import ist eine Objective-C-spezifische Präprozessordirektive zum Importieren von Headerdateien. Der Unterschied zum in C üblichen

#include ist, dass #import sicherstellt, dass Header nur einmal importiert werden. Anders als Javas import oder C#s using macht #import nicht Namen bekannt, sondern liest Deklarationen und eventuell auch Definitionen ein.

#import kann auf zweierlei Weise verwendet werden: zum Import systemweiter Headerdateien und zum Import anwendungsspezi- fischer Headerdateien:

#import <…> importiert einen Systemheader.

#import "…" importiert einen anwendungsspezifischen Header.

Zwischen den spitzen Klammern bzw. Anführungszeichen steht der Name bzw. Pfad zu einer Headerdatei. Die verwendete Form steu- ert, wo nach der entsprechenden Datei gesucht wird, wenn der Name kein absoluter Pfad ist. Bei der ersten Form wird in einer (über Compilerschalter und Umgebungsvariablen erweiterbaren) Liste von Standardverzeichnissen gesucht. Bei der zweiten Form wird zuvor noch im aktuellen Verzeichnis gesucht.

Üblicherweise gibt es zu jeder Codedatei, deren Definitionen in ande- ren Codedateien genutzt werden sollten, eine eigene Headerdatei. Daneben gibt es übergeordnete Header, sogenannte Umbrella-Header, die viele in Zusammenhang stehende Header importieren und so gemeinsam importierbar machen. Der in unserem Beispiel verwendete Foundation.h-Header ist der Umbrella-Header für das Foundation- Framework, das das Fundament der Cocoa-Objective-C-API bildet.

Frameworks

Unter OS X und iOS werden Programmbibliotheken und andere Ressourcen zu sogenannten Frameworks gebündelt. Diese werden üblicherweise in /System/Library/Frameworks und /Library/Frame- works gespeichert und stehen allen Anwendungen zur Verfügung.

Frameworks können unter anderem Headerdateien mit den Dekla- rationen für die Features enthalten, die sie bereitstellen. Ein Frame- work kann mehrere Headerdateien enthalten. Folgendermaßen bin- den Sie einen Header aus einem Framework ein:

#import <Framework/Header.h>

Der Foundation/Foundation.h-Header, den unser Programm nutzt, ist ein Beispiel für einen solchen Framework-Header. Foundation ist das Framework, Foundation.h der Name der Headerdatei im Framework. Die Headerangaben mit #import werden vom Compiler zur entsprechenden Ressource im Framework-Bundle aufgelöst. Das heißt, dass der »Pfad« in der Headerangabe nicht dem tatsäch- lichen Dateipfad der Headerdatei entsprechen muss.

Der Foundation.h-Header ist der Umbrella-Header für das Founda- tion-Framework. Ein Umbrella-Header ist ein Metaheader, der (meist) keine eigenen Deklarationen enthält, sondern die Header für die verschiedenen Elemente eines Frameworks zusammenfasst. Wenn Sie eine Komponente eines Frameworks nutzen, sollten Sie in der Regel einen solchen übergeordneten Header verwenden, nicht die Header für einzelne Komponenten.

Bei der Kommandozeilenkompilation geben Sie Frameworks, gegen die gelinkt werden soll, mit dem Schalter -framework an.

Wenn Sie Xcode nutzen, werden die Frameworks, die standard- mäßig für eine bestimmte Art von Anwendung genutzt werden, automatisch eingebunden. Müssen Sie zusätzliche Frameworks ein- binden, können Sie das für das jeweilige Build-Target im Link Binary With Libraries-Abschnitt der Registerkarte Build Phases tun, den Sie in der Abbildung Abbildung 1-2 sehen:

Phases tun, den Sie in der Abbildung Abbildung 1-2 sehen: Abbildung 1-2: Frameworks einbinden 10 |

Abbildung 1-2: Frameworks einbinden

KAPITEL 2

Syntax

Objective-C erweitert C um eine Reihe von Syntaxstrukturen und Konzepten zur Unterstützung seiner dynamischen Variante der objektorientierten Programmierung. Dieser Abschnitt stellt sie kurz vor, damit Sie den Beispielen in den nachfolgenden Teilen des Buchs folgen können. Komplexere Elemente wie Objekte und Klassen werden an anderer Stelle noch ausführlicher betrachtet.

Klassen und Objekte

Objective-C unterstützt alle C-Datentypen, elementare (z. B. im ersten Beispiel oben char und int) wie komplexe (z. B. Structs oder Arrays). Daneben integriert es Strukturen zur Darstellung von Klassen und Objekten. Klassen sind Anleitungen für den Aufbau von Gegenständen, Objekte sind die konkreten Gegenstände, mit denen Objective-C-Code arbeitet.

Objective-C-Objekte referenzieren Sie in Ihrem Code über Variablen, die Zeiger auf einen Wert des entsprechenden Typs, d. h. der Klasse, sind, oder den generischen Objekttyp id haben, zum Beispiel:

NSString *string = @"Kansas";

oder:

id objekt = @"Kaninchenbau";

Objekte können mit Literalausdrücken (siehe Abschnitt »Literalde- klarationen« auf Seite 14) definiert, von anderen Objekten geliefert oder mithilfe von Klassen konstruiert werden. Ausführlicher behan- deln wir Objekte und Klassen in den Kapiteln 3 und 4.

Nachrichten

Die Interaktion mit Objekten und Klassen erfolgt über Nachrichten. Sie senden einem Empfänger (einer Klasse oder einem Objekt) eine Nachricht. Dieser prüft, ob er mit dieser Nachricht etwas anfangen kann, und reagiert gegebenenfalls entsprechend. Die Syntax zum Senden von Nachrichten hat folgende elementare Gestalt:

[Empfa¨nger Nachricht]

Der Empfänger ist entweder eine im Kontext gültige Klasse oder eine im Kontext gültige Objektreferenz. Die Nachricht ist der Name einer Methode samt aller eventuellen Parameter, die auf dem Emp- fänger aufgerufen wird.

Konkret könnte das so aussehen:

[kaffeeMaschine kaffeeKochen] [Auto neuesAuto]

Da der erste Buchstabe von kaffeeMaschine ein Kleinbuchstabe ist, wäre Ersteres konventionsgemäß eine Nachricht an das Objekt, das von der zuvor deklarierten und initialisierten Objektvariablen kaffeeMaschine referenziert wird. Das Zweite hingegen wäre eine Nachricht an die Klasse Auto, da der erste Buchstabe von Auto ein Großbuchstabe ist.

Nachrichten können parameterlos sein (wie die beiden Nachrichten oben), und sie können Parameter erwarten. Nachrichtenparameter folgen im Nachrichtennamen nach einem Doppelpunkt, wobei der Doppelpunkt Teil des Nachrichtennamens ist. Zum Beispiel sendet

[ich trinken: kaffee]

dem von ich referenzierten Objekt die Nachricht trinken: und übergibt dabei das von kaffee referenzierte Objekt.

Wenn eine Nachricht mehrere Parameter hat, folgen diese nach weiteren Namensteilen, die ebenfalls jeweils mit einem Doppel- punkt abgeschlossen werden:

[ich trinken: kaffee essen: kuchen];

oder:

[ich trinken: kaffee essen: kuchen mitPerson: mrPresident];

Beachten Sie, dass zwischen den Parametern keine Kommata ste- hen. Mehr zu den Regeln für die Namen und die Nutzung von Nachrichten erfahren Sie im Kapitel 12, Messaging, bei der Beschrei- bung der Definition eigener Nachrichten.

Nachrichten sind Ausdrücke, die entweder zu einem Wert (einer Objektreferenz oder einem elementaren C-Wert, z. B. int) aus- gewertet werden oder leer (void) sind. Nachrichten, die nicht void sind, können geschachtelt werden, zum Beispiel:

if ([kaffeeMaschine kannKaffeeKochen]) { [ich trinken: [kaffeeMaschine kaffeeKochen]];

}

Funktionell können Sie sich das Senden einer Nachricht als Äquiva- lent zum Aufruf einer Methode vorstellen. Eine Nachricht an eine Klasse entspricht dem Aufruf einer Klassenmethode, eine Nachricht an ein Objekt dem Aufruf einer Instanzmethode. [objekt machDas] entspräche also einem objekt.machDas() in Java und ähnlich struktu- rierten Programmiersprachen. Das vorangehende Codefragment könnte in einer solchen Programmiersprache also folgendermaßen aussehen:

if(kaffeeMaschine.kannKaffeeKochen()) { ich.trinken(kaffeeMaschine.kaffeeKochen());

}

Wie Sie eigene Nachrichten definieren, erfahren Sie in Kapitel 4 im Abschnitt »Methoden« auf Seite 38, wie der Benachrichtigungs- mechanismus intern funktioniert und wie Sie in ihn eingreifen können, in Kapitel 12, Messaging.

Compilerdirektiven

Compilerdirektiven sind Anweisungen an den Compiler, bestimm- ten Code zu generieren. Sie haben immer folgende Gestalt:

@name

Compilerdirektiven werden zu den unterschiedlichsten Zwecke ein- gesetzt: zur Deklaration und Definition von Klassen, zur Deklaration von Protokollen, für die Ausnahmeverarbeitung, für die Speicher- verwaltung usw. Beispiele sind unter anderem die Direktiven

@implementation und @end, die eine Klassendefinition einrahmen, @class, die einen Klassennamen bekannt macht, oder @private, @public usw., die die Sichtbarkeit von Instanzvariablen steuern.

Die @autoreleasepool-Direktive aus dem einleitenden Beispiel be- zieht sich beispielsweise auf den nachfolgenden Block (den gesam- ten Code in den geschweiften Klammern) und dient der Speicher- verwaltung. Sie weist den Compiler an, Code zu generieren, der dafür sorgt, dass für den Block ein Auto-Release-Pool verfügbar ist. Dass ein solcher verfügbar ist, wird von den Cocoa-Bibliotheken vorausgesetzt, auf die der Code zurückgreift. Wäre kein Auto- Release-Pool verfügbar, würde das zu Speicherlöchern führen.

Wir werden die verschiedenen Compilerdirektiven in den Abschnit- ten zu den entsprechenden Themen behandeln. Eine Aufstellung aller Compilerdirektiven finden Sie im Kapitel 18, Alternative Platt- formen.

Literaldeklarationen

Objective-C definiert ein paar neue Syntaxformen für Literale, mit denen einige häufig genutzte Objekttypen leichter erstellt werden können. Wie Compilerdirektiven werden auch Literaldarstellungen mit dem @-Zeichen eingeleitet. Von einfachen Compilerdirektiven unterscheiden sie sich dadurch, dass auf das @-Zeichen ein weiteres Nicht-Buchstabenzeichen (also [, {, ") oder ein gewöhnliches Zahl- literal, folgt.

Objective-C definiert Literale für Strings (@"…"), Arrays (@[]), Dictio- naries (@{…}) und für Zahlobjekte (z. B. @2.1415). Alle Literale erstel- len Objekte, die unveränderlich sind. Diese Arten von Literalen werden wir uns in den folgenden Unterabschnitten kurz ansehen.

wir uns in den folgenden Unterabschnitten kurz ansehen. Array-, Dictionary- und Zahlliterale gibt es erst seit

Array-, Dictionary- und Zahlliterale gibt es erst seit Mac OS X 10.8 bzw. iOS 6.0, und sie werden von den Entwicklungswerkzeugen erst seit Clang 4.0 unterstützt.

Stringliterale

@"…" definiert ein Objective-C-Stringliteral. Objective-C-Stringlite- rale werden vom Compiler standardmäßig in Objective-C-Objekte des Typs NSString umgewandelt. @"…"-Stringliterale sind also im Gegensatz zu C-Strings (denen kein @-Zeichen vorangestellt wird, z. B. "Ein C-Stringliteral") Objekte und verhalten sich deswegen wie die Stringobjekte anderer objektorientierter Programmierspra- chen. Die Klasse NSString bringt bereits eine Menge von Methoden mit, mit denen Sie Objective-C-Strings verarbeiten können.

mit, mit denen Sie Objective-C-Strings verarbeiten können. Die vom Compiler für Objective-C-Stringlite- rale zu

Die vom Compiler für Objective-C-Stringlite- rale zu verwendende Klasse kann mit dem

Compilerschalter

-fconstant-string-class

eingestellt werden.

Array-Literale

@[…] definiert ein Array-Literal. Die Elemente des Arrays werden durch Kommata getrennt zwischen den eckigen Klammern angege- ben:

@[@"eins", @"zwei", @"drei"]

Array-Literale werden vom Compiler in NSArray-Objekte übersetzt. NSArray-Objekte können nur Objekte enthalten, aber diese Objekte können beliebigen Typs sein, d. h., ein Array kann Instanzen verschiedener Klassen enthalten. Folgendermaßen könnte man ein Array deklarieren, das einen String und ein weiteres Array mit zwei Zahlen enthält:

@[@"12-12-12", @[@48.857731, @2.339407]];

Das zweite Array enthält zwei Zahlen, die als Zahlobjekte angege- ben werden müssen, da NSArray-Objekte nur Objekte enthalten können. Literale für Zahlobjekte werden weiter unten in diesem Abschnitt beschrieben. Literaldeklarationen können zur Verbes- serung der Lesbarkeit wie im folgenden (etwas extremen) Beispiel über mehrere Zeilen aufgeteilt werden:

NSArray *position = @[

@"12-12-12",

@[

@48.857731,

@2.339407

]

];

Dictionary-Literale

@{…} definiert ein Dictionary-Literal. Dictionaries oder Wörterbü- cher verknüpfen einen Schlüssel, der ein beliebiger Objektwert sein kann, mit einem Wert, der ebenfalls ein beliebiger Objektwert sein kann. Zwischen Schlüssel und Wert steht jeweils ein Doppelpunkt (Schlu¨ssel:Wert). Mehrere Schlüssel/Wert-Paare werden als kom- maseparierte Liste angegeben:

@{

 

@"datum"

: @"12-12-12",

@"koords" : @[@48.857731, @2.339407]

};

Dictionary-Literale werden vom Compiler in NSDictionary-Objekte übersetzt. Als Dictionary-Schlüssel können normalerweise beliebige Objektwerte verwendet werden, vorausgesetzt, die Objekt unter- stützen das Protokoll NSCopying (das in Kapitel 9, Objektlebens- zyklus, im Abschnitt »Objekte kopieren« auf Seite 95 beschrieben wird). Die Schlüssel müssen Strings sein, wenn das Dictionary Key/Value-Coding konform sein soll (siehe Kapitel 13, Key/Value- Coding).

Literale fu¨ r Zahlobjekte

In Objective-C können die skalaren C-Zahltypen, z. B. int oder float, genutzt werden. Außerdem definieren die Cocoa-Bibliothe- ken eigene skalare Zahltypen, z. B. NSInteger oder CGFloat, die eine konsistente Zahldarstellung auf unterschiedlichen Plattformen (32 Bit vs. 64 Bit) gewährleisten. Daneben bieten die Cocoa-Biblio- theken den Objekttyp NSNumber, der als Objekt-Wrapper für die verschiedenen Zahltypen dient. Dieser wird z. B. benötigt, wenn

Zahlen in einem NSArray-Objekt gespeichert werden sollen, da die Cocoa-Collection-Typen nur Objekte aufnehmen können.

Zahlobjekte können durch eine der vielen NSNumber-Klassennach- richten oder mithilfe von numerischen Objektliteralen erstellt wer- den. Ein numerisches Objektliteral wird deklariert, indem einem gewöhnlichen Zahlliteral ein @-Zeichen vorangestellt wird, zum Beispiel:

@3

@3.1415 // Entspricht dem double-Wert 3.1415

// Entspricht dem int-Wert 3

@YES

// Entspricht dem BOOL-Wert YES

@'a'

// Entspricht dem char-Wert 'a'

Wenn der gewünschte repräsentierte skalare Wert vom Standardtyp für Zahlliterale des entsprechenden Typs abweicht, kann dieser durch ein Postfix (U, L, F) angegeben werden, zum Beispiel:

@3L

// long-Wert

@3U

// unsigned int-Wert

@3.1415F // float-Wert

@3U // unsigned int-Wert @3.1415F // float-Wert Seit Mac OS X 10.7 nutzt Objective-C für NSNumber

Seit Mac OS X 10.7 nutzt Objective-C für NSNumber-Objekte sogenannte Tagged Pointer. Das sind markierte Zeiger, die in den freien Bits der Zeigeradresse direkt den Wert und einen Typcode für das Objekt enthalten. Da Objective-C damit die teurere Objekterstellung und -auflösung spart, können die NSNumber- Zahlobjekte erheblich leichtgewichtiger und schneller als die Wrapper-Klassen anderer Pro- grammiersprachen sein.

Auf das @-Zeichen muss ein Zahlliteral folgen, d. h., Folgendes geht nicht:

int zahl

NSNumber *zahlobjekt = @zahl; // Geht nicht

=

3;

Nutzen Sie stattdessen die im nächsten Abschnitt vorgestellten Wrapper-Ausdrücke.

Wrapper-Ausdru¨ cke

Wrapper-Ausdrücke (Apple bezeichnet sie als »Boxed Expressi- ons«) ermöglichen Ihnen, die Werte von C-Ausdrücken in den korrespondierenden Objective-C-Objekttyp umzuwandeln. Wrap- per-Ausdrücke nutzen die folgende Syntax:

@(…)

Einen Wrapper-Ausdruck könnten Sie folgendermaßen nutzen, um einen skalaren Zahlwert in ein Zahlobjekt umzuwandeln:

int zahl

NSNumber *zahlobjekt = @(zahl); // Geht

=

3;

Wrapper-Ausdrücke unterstützen neben numerischen Ausdrücken Enums, BOOL und C-Strings, zum Beispiel:

int zahl

NSNumber *istUngerade = @((BOOL)zahl%2); char *cString = "Hallo"; NSString *objcString = @(cstring);

=

3;

Blocks

Blocks ist eine Syntax zur Definition von Funktionsausdrücken. Sie wurden von Apple zur Übernahme in den C-Standard eingereicht und kommen unter Objective-C immer häufiger zum Einsatz. Sie werden unter anderem unter GCD (Grand Central Dispatch), der von Apple eingeführten Parallelisierungsinfrastruktur für Cocoa, genutzt, aber auch zur Definition einfacher Delegate-Funktionen, z.B. beim Vergleich oder bei der Enumeration von Objekten.

Blocks können inline definiert oder Blockvariablen zugewiesen werden.

Eine Blockvariable wird folgendermaßen deklariert:

Ru¨ckgabetyp (^Variablenname)(Parameterliste)

Eine Blockdefinition hat folgende Gestalt:

^(Parameterliste) {Anweisungen}

Folgendermaßen könnte man einen einfachen Block definieren, der zwei Strings verkettet:

NSString * (^str_concat)(NSString *, NSString *) = ^(NSString *head, NSString *tail) { return [NSString stringWithFormat: @"%@%@", head, tail];

}

Die Deklaration der Blockvariablen str_concat sagt, dass dieser Block zwei NSStrings erwartet und einen NSString liefert. Diesen Block würden Sie folgendermaßen nutzen:

str_concat(@"Kopf und ", @"Schwanz");

Das diesen Abschnitt abschließende Beispiel enthält ein Blockliteral, das eine Funktion beschreibt, die bei der Enumeration eines Arrays auf allen Elementen aufgerufen wird. Eine ausführlichere Beschrei- bung des Blocks-Konzepts finden Sie im Abschnitt »Blocks« auf Seite

18.

Ein zweites Beispiel

Schauen wir uns nun ein nicht mehr ganz so primitives Beispiel an, in dem diese Syntaxstrukturen zur Anwendung kommen. Sie sollten jetzt in der Lage sein, diesem Code zu folgen:

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[])

{

@autoreleasepool { id preise = @[@"ein Zeitungsabo", @"eine Kaffeemaschine", @"einen Computer", @"eine Reise", @"ein Auto"

];

NSUInteger anzahl = [preise count];2 int zufall = arc4random_uniform((UInt32)anzahl);3

id verlosung = [NSMutableString new];4 [verlosung appendString: @"Sie ha¨tten"];5 [preise enumerateObjectsUsingBlock: 6 ^(id obj, NSUInteger idx, BOOL *stop) {

1

id format =

idx

==

0

? @"%@"

:

idx < anzahl

-

1

?

@",

%@"

:

@" oder %@";7 [verlosung appendFormat: format, obj];8

}];9

[verlosung appendString: @" gewinnen ko¨nnen."];:

NSLog(@"%@", verlosung);;

if (zufall < anzahl

-

2)

{<

 

NSLog(@"Sie haben %@ gewonnen.", [preise objectAtIndex:

zufall]);=

}

else { NSLog(@"Sie haben leider nichts gewonnen.");

}

}

return 0;

}

1

Erstellt mithilfe eines Literalausdrucks ein Array und weist es der Variablen preise zu, deren deklarierter Typ id ist.

2

Ruft mit der Nachricht count die Anzahl an Elementen im Array preise ab. Der Wert wird in der Variablen anzahl gespeichert, die den Typ NSUInteger hat. Das ist der Rück- gabetyp für count.

3

Erzeugt mithilfe der C-Funktion arc4random_uniform() eine Zufallszahl zwischen 0 und der Anzahl an Array-Elementen. Der Cast auf (UInt32) ist erforderlich, weil die Funktion einen int erwartet und liefert, anzahl aber den Typ NSUInteger hat.

4

Sendet der Klasse NSMutableString die new-Nachricht. Auf diese erhalten wir ein Objekt, das einen veränderbaren String repräsentiert. Wir werden es nutzen, um einen Satz aufzubau- en, der die Elemente des Arrays aufzählt.

5

Sendet dem gerade erstellten Objekt über die Referenz ver-

losung die Nachricht appendString: mit dem Text @"Sie

ha¨tten " als Argument. Das bewirkt, dass der Text an den, noch leeren, String angehängt wird.

6

Sendet dem preise-Objekt die Nachricht enumerateObjects UsingBlock: mit einer Blockdefinition als Argument. Diese Nachricht bewirkt, dass das Array durchlaufen und alle seine Elemente mit dem Block verarbeitet werden.

9

Der Block, der auf die Array-Elemente angewandt wird. Der Block erhält drei Parameter, von denen uns nur die beiden ersten interessieren: obj, das aktuelle Array-Element, und idx, der Index des Elements.

7

Ermittelt mithilfe des ternären Bedingungsoperators einen für die Position des aktuellen Array-Elements geeigneten For- matstring.

8 Sendet verlosung die Nachricht appendFormat: mit dem ermit- telten Format und dem aktuellen Objekt als Argument. Hängt den Wert des aktuellen Array-Elements mit dem Format for- matiert an den String an.

: Schließt den String, nachdem die Elemente des Arrays in ihn eingebaut wurden, mit einer weiteren appendString:-Nach- richt.

; Protokolliert den String mit der Bibliotheksfunktion NSLog() auf der Konsole.

< Prüft, ob die Zufallszahl einen Gewinn darstellt, und sorgt dafür, dass die entsprechende Ausgabeanweisung zur Ausfüh- rung kommt. (Sie fragen sich, was das -2 soll? Haben Sie sich noch nie gewundert, warum Sie den Hauptgewinn nicht be- kommen haben?)

= Nutzt einen Objektindexer, um ein Element aus einem Array- Objekt abzurufen.

Die Ausgabe könnte folgendermaßen aussehen (wir haben den Text etwas formatiert, um ihn besser lesbar zu machen):

2012-12-08 17:13:29.233 Gewinnspiel[20151:303] Sie ko¨nnen ein Zeitungsabo, eine Kaffeemaschine, einen Computer, eine Reise oder ein Auto gewinnen. 2012-12-08 17:13:29.236 Gewinnspiel[20151:303] Sie haben eine Kaffeemaschine gewonnen.

Sie haben eine Kaffeemaschine gewonnen. Wenn Sie so ungefähr erkennen konnten, wie der Code in

Wenn Sie so ungefähr erkennen konnten, wie der Code in diesem Beispiel funktioniert, soll- ten Sie dazu in der Lage sein, dieses Buch zu nutzen. Ist das jedoch nicht der Fall, sollten Sie sich zunächst mit einem geeigneten Buch hin- reichende Kenntnisse einer anderen Program- miersprache aneignen (am besten C oder eine Programmiersprache, deren Syntax an C ange- lehnt ist).

KAPITEL 3

Objekte

Objekte sind der Angelpunkt der objektorientierten Programmie- rung. Sie sind der Stoff, aus dem Ihre Programme gewebt sind.

Für die, die noch nie mit einer objektorientierten Programmier- sprache gearbeitet haben: Alle Programme bestehen aus Daten und Operationen. Bei prozeduralen Programmiersprachen wie C sind das jeweils eigenständige Programmkomponenten: Daten, die in einem Speicher festgehalten und über Variablen im Programm referenziert werden, und Funktionen, die auf diese Daten zugreifen. In objektorientierten Programmiersprachen werden Daten und Operationen zu Einheiten zusammengefasst, die als Objekte be- zeichnet werden.

Wie in den meisten anderen objektorientierten Programmierspra- chen sind Objekte auch in Objective-C opake Strukturen, die dyna- misch in einem speziellen Speicherbereich, dem Heap, angelegt und verwaltet werden. Sie können nur über Referenzen festgehalten, manipuliert und zwischen Programmteilen ausgetauscht werden.

Objektvariablen und Zeiger

In Objective-C werden Objektreferenzen durch klassische C-Zeiger repräsentiert und implementiert. Ein C-Zeiger ist eine Variable, die die Adresse eines Speicherorts festhält. Zeigervariablen werden folgendermaßen deklariert:

Typ *Variablenname;

Der Zeigerindikator * zeigt an, dass eine Variable deklariert wird, die einen Zeiger auf einen Wert des angegebenen Typs enthält. Der

* wird entweder nach dem Typ oder vor dem Variablennamen angegeben (in Objective-C ist die Stellung vor dem Variablennamen üblicher).

Es gibt zwei Operatoren zur Interaktion mit Zeigervariablen: & ist der Adressoperator, * der Dereferenzierungsoperator. &Variable lie- fert die Speicheradresse einer Variablen, *Variable den Wert an der Speicheradresse, die eine Zeigervariable festhält.

Einen Zeiger, der die Adresse eines int-Werts festhält, würde man in C also folgendermaßen deklarieren:

int *zahlzeiger;

Den Wert einer anderen Variablen weisen Sie dem Zeiger mithilfe des Adressoperators zu:

int zahl

int *zahlzeiger = &zahl;

=

42;

Den Wert, auf den ein Zeiger zeigt, rufen Sie mithilfe des Derefe- renzierungsoperators ab:

int zahl

int *zahlzeiger = &zahl; NSLog(@"Die Antwort ist %i", *zahlzeiger);

=

42;

Schauen wir uns den Unterschied zwischen einem Zeiger auf einen Wert eines bestimmten Typs (int *) und einem Wert eines be- stimmten Typs (int) anhand eines Beispiels an:

int

int

int *zeiger = &a;

a

NSLog(@"b ist %i, zeiger zeigt auf %i",b , *zahl);

// Liefert "b ist

=

a

b

2;

=

=

1;

a;

1, zeiger

zeigt auf 2"

a und b sind gewöhnliche Werttypvariablen. Wird b der Wert von a zugewiesen, wird einfach der Wert von a kopiert. Spätere Änderun- gen von a wirken sich nicht auf den Wert von b aus. b behält also den Wert 1, den a hatte, als sein Wert b zugewiesen wurde.

zeiger hingegen ist ein Zeiger auf einen Wert des Typs int. Einer solchen Variablen wird kein Wert, sondern die Adresse eines Werts zugewiesen. Ändert sich der Wert an jener Adresse, ist das über die Zeigervariable sichtbar. zeiger zeigt auf die Adresse, an der a gespeichert ist, und die Dereferenzierung von zeiger liefert stets den aktuellen Wert von a.

Zeiger auf Zeiger bieten eine weitere Ebene der Indirektion. Ein Zeiger auf einen Zeiger wird deklariert, indem der * verdoppelt wird. Folgender Code macht zeigerzeiger zu einem Zeiger auf einen Zeiger auf einen int-Wert und weist ihm die Adresse des int-Zeigers zeiger zu:

int zahl

int *zeiger = &zahl int **zeigerzeiger = &zeiger;

=

42;

In C (und C++) werden Zeiger auf Zeiger z. B. häufig zur Realisie- rung mehrdimensionaler Arrays eingesetzt. In Objective-C (d. h. Cocoa) werden sie eingesetzt, um Zeiger auf Objekte zu übergeben. Ein Beispiel dafür und eine Illustration dieser Technik finden Sie in Kapitel 7, Fehler und Ausnahmen.

Technik finden Sie in Kapitel 7, Fehler und Ausnahmen . Beachten Sie, dass viele der in

Beachten Sie, dass viele der in C üblichen Zeigermanipulationen unter Objective-C bei Zeigern auf Objective-C-Objekte nicht zulässig sind.

Dynamisch typisierte Variablen: der Typ id

Objective-C definiert einen speziellen statischen Typ für Objekt- variablen: id. id ist als Zeiger auf ein C-Struct definiert, das nur ein einziges Feld, ein isa-Feld des Typs Class, hat. -Class ist ein weiterer Struct-Typ, der als eine opake Referenz auf eine Klasse dient.

Mit einer Variablen des Typs id können Sie beliebige Arten von Objekten festhalten, zum Beispiel:

id text = @"Die Welt ist alles, NSLog(@"%@", text);

was der Fall ist";

Weisen Sie einer id-Variablen ein Objekt zu, sorgt die Objective- C-Laufzeitumgebung dafür, dass deren Class-Feld entsprechend initialisiert wird. Nutzen Sie ein über eine id-Referenz festgehalte- nes Objekt, sorgt die Laufzeitumgebung dafür, dass die entspre- chenden Objektoperationen angefordert werden.

Bei Variablen des Typs id prüft der Compiler nicht, ob die ange- forderten Operationen unterstützt werden. Sie können auf einer id-Referenz also beliebige Operationen anfordern, ohne dass Sie die id-Referenz zuvor in eine Referenz eines Typs umwandeln müssen, für den eine entsprechende Operation tatsächlich definiert ist.

id ist also nicht mit Javas Object vergleichbar, sondern eher mit dem dynamic-Typ von C#.

Statisch typisierte Variablen

Objekte können auch durch typisierte klassische C-Zeiger repräsen- tiert werden. Wenn ein Objekttyp namens Objekt definiert ist, können Sie also, analog zur *zahl-Deklaration oben, folgenderma- ßen eine *objekt-Variable definieren:

Objekt *objekt = //Irgendwie ein Objekt erzeugen

Diese Variable vom Typ Objekt * speichert also eine Speicher- adresse, an der ein Objekt des Typs Objekt gespeichert wird. Eigentlich interessiert Sie daran aber nur, dass sie Ihnen als Referenz auf den dort gespeicherten Objektwert dient:

Dass Ihre Variable ein Zeiger ist, ist, wenn Sie im Objective-C-Kon- text bleiben, nur bei der Deklaration sichtbar. In Ihrem Code gehen Sie mit ihr (in der Regel) nicht anders um als mit einer gewöhnlichen Referenzvariablen in anderen objektorientierten Programmierspra- chen.

Statisch mit einem konkreten Typ typisierte Variablen haben den Vorteil, dass der Compiler statisch prüfen kann, ob die auf der Referenz angeforderten Operationen von der entsprechenden Art von Objekt unterstützt werden. Der Eigenschaftszugriff über die Punktnotation und der direkte Feldzugriff werden nur bei statisch typisierte Variablen unterstützt.

Objekte erstellen

Will ein Programm mit einem Objekt arbeiten, muss das System im Speicher Platz dafür schaffen und diesen ordentlich initialisieren. In Java, C# und vielen anderen Sprachen werden all diese Operatio- nen angefordert und im Hintergrund abgewickelt, wenn eine Kons- truktorfunktion mit dem new-Operator aufgerufen wird. Objec- tive-C kennt keinen new-Operator. Stattdessen werden die notwendigen Arbeiten explizit angefordert, indem Nachrichten eingesetzt werden, die konventionsgemäß bestimmte Operationen wie die Reservierung und Initialisierung des Speichers für ein Ob- jekt vornehmen.

Diese Nachrichten sind die Klassennachrichten +alloc und +new (diese Nachricht ist Ihnen in unserem zweiten Beispiel bereits begegnet) und die Objektnachricht init (bzw. Objektnachrichten, deren Name mit init beginnt). alloc sorgt dafür, dass der Speicher- platz für ein Objekt bereitgestellt wird, init dafür, dass dieser Speicherplatz korrekt initialisiert wird. Üblicherweise werden diese Methoden folgendermaßen gemeinsam verwendet:

id ding = [[Ding alloc] init];

new ist einfach ein Alias für diese Operation, d. h., folgende Zeile ist der vorangegangenen vollkommen äquivalent:

id ding

= [Ding new];

Die Basisklasse der Cocoa-Plattform, NSObject, stellt die Grund- implementierungen für diese Nachrichten bereit. Eine ausführliche Beschreibung finden Sie in Kapitel 9, Objektlebenszyklus.

Wenn Objekte bei der Erstellung mit bestimmten Werten initiali- siert werden sollen, können Klassen Initialisierungsmethoden an- bieten, die Parameter erwarten. Diese haben konventionsgemäß einen Namen, der mit init beginnt. Beispielsweise bietet die Klasse NSString eine initWithFormat:-Methode, mit deren Hilfe ein String- objekt auf Basis eines Formatstrings initialisiert werden kann. Wie Sie die Initialisierungsmethoden Ihrer eigenen Klassen gestalten, wird in Kapitel 9, Objektlebenszyklus, beschrieben.

Mit Objekten interagieren

In Objective-C haben Objekte Felder und Eigenschaften und ver- stehen Nachrichten. Gemeinsam bilden diese Komponenten die Schnittstelle, über die Ihr Code mit Objekten interagieren kann. Felder stellen die Daten Ihres Objekts dar und sind einfach an spezifische Objekte gebundene Variablen. Eigenschaften sind Kons- trukte, bei denen der Zugriff auf ein Feld über Codeeinheiten gesteuert und so von der Implementierung der Datenspeicherung isoliert wird. Die Nachrichten, die ein Objekt versteht, sind die Operationen, die ein Objekt beherrscht, und entsprechen in der Regel einer Codeeinheit, die mit dem Objekt als Kontext zur Aus- führung kommt.

Das C-Erbe führt dazu, dass in Objective-C drei verschiedene Syntaxformen für diese drei Elemente der Schnittstelle von Objek- ten existieren:

1. Mit der Pfeilnotation, dem klassischen Dereferenzierungsope- rator von C, greifen Sie auf die Felder des referenzierten Objekts zu:

objekt->nr = objekt->nr + 1;

2. Mit der Punktnotation interagieren Sie mit seinen Eigenschaf- ten:

objekt.anzahl = objekt.anzahl + 5;

3. Über die Nachrichtensyntax mit eckigen Klammern senden Sie dem Objekt eine Nachricht, was in der Regel dazu führt, dass der Code in einer Methode ausgeführt wird:

Objekt *kopie = [objekt kopieren];

Beachten Sie, dass der Feld- und der Eigenschaftszugriff nur dann möglich ist, wenn die jeweiligen Referenzen statisch typisiert sind und der Compiler statisch prüfen kann, ob die entsprechenden Operationen unterstützt werden. Das bedeutet, dass Sie über Refe- renzen des Typs id nicht (direkt) auf Felder zugreifen können, während der Eigenschaftszugriff über den direkten Aufruf der Accessor-Methoden weiterhin möglich bleibt.

Ein Beispiel:

Schiff *titanic = schiff_bauen(@"Titanic");

Der Schiff *-Zeiger titanic zeigt jetzt auf ein Schiff-Objekt – vorausgesetzt, die C-Funktion schiff_bauen() liefert einen Zeiger auf ein Objective-C-Objekt mit dem Typ Schiff. Wir können über diesen Zeiger nun alle Operationen anfordern, die für den Typ Schiff definiert sind – ganz wie man es erwartet, wenn man mit streng typisierten Programmiersprachen wie C oder Java vertraut ist.

NSString *name = titanic->name; [titanic auslaufen]; titanic.schubProzent = 50;

Nutzen Sie hingegen eine id-Referenz, um ein Objekt festzuhalten, können Sie auf dieser Referenz nur noch Operationen vornehmen, die der Compiler nicht statisch prüfen muss:

id titanic = schiff_bauen(@"Titanic"); [titanic auslaufen]; [titanic setSchubProzent: 50];

Bei einer id-Referenz haben Sie keinen Zugriff auf das name-Feld. Auf die schubProzent-Eigenschaft können Sie zugreifen, müssen dabei aber die Zugriffsmethoden nutzen, hier den Setter -setSchub

Prozent:.

Objekte vergleichen

Objekte werden über Zeiger festgehalten. Werden Zeiger über den ==-Operator verglichen, werden einfach die Werte der Zeiger, d. h. die Speicheradressen, die sie speichern, verglichen. Zwei Objekt- zeiger werden gemäß ==-Operator also nur dann als gleich betrach- tet, wenn sie auf dasselbe Objekt zeigen, zum Beispiel:

id text = @"123";

id gleicherText = [NSString stringWithFormat:@"%@", @"123"]; id gleicheReferenz = text;

BOOL test 1 = text == gleicherText;

BOOL test 2 = text == gleicheReferenz; // YES

//

NO

Wenn die Objekte einer Klasse ein anderes Vergleichsverhalten aufweisen sollen, muss die Klasse entsprechende Einrichtungen bereitstellen. Alle Objekte besitzen eine isEqual:-Methode, die standardmäßig das gleiche Verhalten aufweist wie der ==-Operator. Klassen können diese überschreiben, um ein anderes Vergleichs- verhalten zu definieren (siehe Kapitel 11, Laufzeitinformationen). Manche Klassen definieren darüber hinaus spezielle Vergleichs- methoden. Beispielsweise definiert die Klasse NSString eine isEqualToString:-Nachricht, die Stringvergleiche schneller durch- führt, wenn sichergestellt ist, dass beide Objekte Strings sind.

Objekt-Enumeration

Wenn ein Objekt ein Container, d. h. ein Behälter ist, der andere Objekte enthält, können seine Elemente mit der for (Typelement in container)-Schleifenstruktur enumeriert werden. Die Schnittstelle für diese schnelle Enumeration wird im Framework-Protokoll NSFastEnumeration definiert. container kann nur ein Objekt sein, dessen Klasse dieses Protokoll implementiert. NSFastEnumeration

wird z. B. von

NSSet sowie von

NSEnumerator implementiert.

Zum Beispiel könnten wir die [preise enumerateObjectsUsingBlock:

…]-Nachricht im abschließenden Beispiel in Kapitel 2, Syntax, fol- gendermaßen durch eine schnelle Enumeration ersetzen:

NSArray,

NSDictionary

und

NSUInteger idx = 0; for (id obj in preise)

{

id format =

idx

==

0

? @"%@"

:

idx < anzahl

-

1

?

@",

%@"

:

@" oder %@"; [verlosung appendFormat: format, obj];

idx++;

}

Objektindizierung

Die von Objective-C seit Xcode 4.4 (LLVM 4.0) unterstützte Ob- jektindizierung gestattet die Referenzierung der Elemente eines Col-

lection-Objekts oder eines Collection-ähnlichen Objekts über die klassische Array-Indizierungssyntax:

Objekt[Index]

Die Objektindizierung wird von den Cocoa-Klassen NSArray, NSSet und NSDictionary sowie ihren veränderlichen Unterklassen unter- stützt.

Objekt[Index] kann als Ausdruck eingesetzt werden, der zum Wert des entsprechenden Elements ausgewertet wird, oder als Lvalue, um den Wert des entsprechenden Elements zu setzen (wenn Objekt eine veränderliche Collection repräsentiert). Zum Beispiel werden im nachfolgenden Code die Elemente des veränderlichen Arrays quadrate auf das Quadrat des Werts des entsprechenden Elements im Array zahlen gesetzt:

id zahlen = @[@0, @1,@2];

id quadrate = [NSMutableArray new];

for (int

i

=

0;

i

< [zahlen

count]; i++) {

quadrate[i] = @( [zahlen[i] integerValue]*[zahlen[i] integerValue]);

}

Die Array-Werte müssen für die arithmetische Operation erst mit der Nachricht -integerValue in elementare Zahlwerte umgewandelt werden (da die numerischen Operatoren für Objekttypen nicht unterstützt werden). Das Ergebnis hingegen muss vor der Zuwei- sung in ein Objekt umgewandelt werden, wozu hier ein Wrapper- Ausdruck genutzt wird, da die »Collection-Typen« nur Objektwerte speichern. Aus gleichem Grund werden bei Indizierungsausdrücken die zusammengesetzten Zuweisungsoperatoren nicht unterstützt.

Index kann (wie oben) ein ganzzahliger Wert oder ein Objektwert sein. Ist der Index ein ganzzahliger Wert, wird eine Array-Indizie- rung genutzt, ist er ein Objektwert, eine Dicitionary-Indizierung.

Beachten Sie, dass sich die Unveränderlichkeit der unveränderlichen Collection-Klassen nicht nur auf die Länge bezieht. Dass sie unver- änderlich sind, heißt auch, dass sie keine Nachrichten zur Änderung ihrer Elemente unterstützen. Folgende scheinbar unschuldige und vom Compiler anstandslos akzeptierte Alternative zu unserem obi-

gen Code wird zur Laufzeit fehlschlagen, weil die gesendete Nach- richt nicht unterstützt wird:

id zahlen = @[@0, @1,@2];

for (int

i

=

0;

i

< [zahlen

count]; i++) {

// Funktioniert nicht!!! zahlen[i] = @( [zahlen[i] integerValue]*[zahlen[i] integerValue]);

}

Der Compiler kann die Operation nur prüfen, wenn die Variable mit einem konkreten statischen Typ typisiert ist. Ändern Sie den Typ von zahlen in NSArray *, meldet der Compiler einen Fehler, wenn der Indizierungsausdruck als Lvalue eingesetzt wird.

Wie bei Objective-C üblich, sind auch Indizierungsausdrücke nur syntaktischer Zucker, der vom Compiler zu den entsprechenden Nachrichten aufgelöst wird. Bei der Array-Indizierung sind das

objectAtIndexedSubscript:

und

setObject:atIndexedSubscript:,

bei der Dictionary-Indizierung objectForKeyedSubscript: und setObject:forKeyedSubscript:. Sie können die Objektindizierung also auch auf Ihren eigenen Klassen unterstützen, indem Sie die entsprechenden Methoden implementieren. Es gibt allerdings kein

Protokoll, das die entsprechende Schnittstelle beschreibt.

KAPITEL 4

Klassen

Objective-C nutzt das klassenbasierte Modell der objektorientierten Programmierung. Jedes Objekt ist eine Instanz einer Klasse; anony- me, allgemeine Objekte wie z. B. in JavaScript gibt es nicht.

In logischer Hinsicht sind Klassen Typdefinitionen, die die »Eigen- schaften« und das »Verhalten« der Objekte bestimmen, die Instan- zen dieser spezifischen Klasse sind. Strukturell stellen sie die Ein- heiten dar, in die Sie Ihren Code gliedern. Sie arbeiten in Ihrem Code mit Objekten, aber wie sich die Objekte verhalten und wozu sie genutzt werden, bestimmen Sie in den Klassen, die Sie schreiben. In Ihrem Code sind Klassen Objekte, mit denen Sie ebenso arbeiten können wie mit den Objekten, die Instanzen von Klassen sind. In diesem Abschnitt werden wir uns mit den ersten beiden Aspekten befassen.

In Objective-C sind die Deklaration und die Definition einer Klasse zwei separate Einheiten. Zunächst wird die Schnittstelle einer Klasse deklariert, d. h., es wird bekannt gemacht, was nach außen und innen sichtbar ist und für den Code der Klasse bzw. für Code, der Objekte des durch die Klasse definierten Typs nutzt, sichtbar ist.

Klassendeklaration und -definition

Objective-C nutzt Compilerdirektiven, um die Syntaxstrukturen für die Deklaration und die Definition von Klassen zu markieren. Eine Klassendeklaration wird von der Compilerdirektive @interface eingeleitet, die Klassendefinition von der Compilerdirektive

@implementation. Dementsprechend bezeichnet man die Deklara- tion als die Schnittstelle und die Definition als die Implementierung.

Schnittstelle und Implementierung stehen üblicherweise jeweils in eigenen Dateien. Das ist üblich, aber nicht notwendig.

Schnittstellendeklaration

Die Schnittstelle enthält die Deklaration der Klasse, bietet aber keine Implementierung. Objective-C-Schnittstellen sind wie C-Headerdateien und haben nichts mit Javas Interfaces zu tun. Sie machen die Struktur einer Klasse bekannt, damit ihre Instanzen von anderem Code genutzt werden können, der ihre Implementierung nicht kennt.

Eine Klassendeklaration hat in Objective-C folgende Struktur:

#import "Oberklasse.h"

@interface Klasse : Oberklasse {

// In diesem Abschnitt stehen die Felddeklarationen

}

// Hier stehen die Eigenschafts- und Methodendeklarationen

@end

Eine Klassendeklaration muss die Deklaration der Oberklasse ein- schließen. Das erfolgt wie hier mit der Präprozessordirektive #import.

Die eigentliche Deklaration wird von den Objective-C-Compiler- direktiven @interface und @end eingerahmt. Sie beginnt mit der Angabe des Namens der neuen Klasse. Auf ihn folgen ein Doppel- punkt und der Name der Oberklasse. Die Oberklasse ist die Klasse, die die neu definierte Klasse erweitert. Doppelpunkt und Oberklas- senname entfallen nur, wenn Sie eine neue Basisklasse deklarieren wollen. Der Abschnitt »Vererbung und Polymorphie« auf Seite 56 geht ausführlicher auf diese Aspekte ein.

Die anschließenden geschweiften Klammern sind der Bereich, in dem die Felder oder Instanzvariablen der Klasse deklariert werden.

Methoden- und Eigenschaftsdeklarationen folgen nach der schlie- ßenden geschweiften Klammer.

Schnittstellendateien tragen den Namen der Klasse, die darin de- finiert wird, und die Dateinamenserweiterung *.h. Üblicherweise werden Klassenschnittstellen in eigenständigen Dateien gespeichert – theoretisch kann eine Schnittstellendateien aber beliebig viele @interface-Abschnitte enthalten.

Klassenimplementierung

Die Implementierung bietet die Definitionen zu den Deklarationen der Schnittstelle; in ihr wird der Code für die Methoden formuliert. Implementierungen stehen in Dateien, die üblicherweise die Datei- namenserweiterung .m tragen (.mm oder .M bei Objective-C++). Eine Implementierungsdatei enthält üblicherweise nur die Imple- mentierung einer Klasse, kann theoretisch aber beliebig viele Klas- senimplementierungen enthalten. Auch für Klassen, die keine Methoden enthalten, muss eine (leere) Implementierungsdatei an- gegeben werden. Wird keine Implementierungsdatei angegeben, erzeugt der Compiler keinen Unterstützungscode für die Klasse. Der Form nach sieht eine Klassenimplementierung folgendermaßen aus:

#import "Klasse" @implementation Klasse // Hier steht die Implementierung @end

Eine Implementierung muss immer die eigene Schnittstelle impor- tieren. Der Definitionscode steht zwischen den Compilerdirektiven

@implementation und @end.

Felder

Felder halten die Daten eines Objekts fest. Es sind an die jeweilige Klasseninstanz gebundene Variablen. In Objective-C werden Felder häufig als Instanzvariablen oder kurz Ivars bezeichnet.

Felder deklarieren

Felder werden in der Schnittstelle in einem in geschweiften Klam- mern stehenden Block deklariert.

@interface Kind : Elter { int alter; NSString* name;

}

@end

Felder können privat, geschützt, öffentlich oder paketgebunden sein. Die Sichtbarkeit von Feldern wird bei der Felddeklaration mit den Compilerdirektiven, @private, @protected, @public und @package deklariert.

@private

Privat. Felder sind im Code der Klasse auf der Instanz selbst und auf anderen Instanzen sichtbar.

@protected

Geschützt. Felder sind im Code der Klasse selbst und im Code von ihr abgeleiteter Klassen auf der Instanz selbst und auf anderen Instanzen sichtbar.

@public

Öffentlich. Felder sind überall sichtbar.

@package

Paketsichtbarkeit. Felder sind innerhalb des Programms oder der Bibliothek sichtbar, in dem bzw. der die Klasse implemen- tiert wird. Für paketinternen Code verhalten sich die entspre- chenden Felder wie öffentliche Felder, für paketexternen Code wie private Felder. Diese Sichtbarkeit entspricht der internal- Sichtbarkeit in C# und der Standard- oder Package-Sichtbarkeit in Java.

Eine Direktive gilt jeweils für alle auf sie folgenden Felddeklaratio- nen, bis sie von der nächsten Direktive überschrieben wird. Die Sichtbarkeit wird also in Blöcken und nicht eigens für jedes Feld deklariert, wie es z. B. in Java üblich ist. Deklarationen, denen keine Sichtbarkeitsdirektive voransteht, erhalten die Sichtbarkeit

@protected.

@interface Agent : Beamter {

Auftrag *auftrag; //Implizit @protected @private Ort *position; @protected NSString *codeName; @public NSString *name; Agent *gegner;

}

@end

Felder referenzieren

Felder können mit dem ->-Operator referenziert werden, wenn die Referenz statisch typisiert ist. Beispielsweise greift agent->name auf das name-Feld eines (fiktiven) Agent-Objekts agent zu.

Auf die Felder der aktuellen Instanz kann im Code der Klasse ohne Qualifikation durch das Objekt zugegriffen werden. Das bedeutet, self->name und name sind im Klassencode äquivalent.

Objekte erhalten alle Felder, die in den Schnittstellen der Klasse selbst und ihrer Oberklassen deklariert sind. Neben den in der eigenen Schnittstelle deklarierten Feldern können auch alle nicht als @private markierten Felder auf diese Weise referenziert werden.

markierten Felder auf diese Weise referenziert werden. Das heißt natürlich auch, dass es unmöglich ist, über

Das heißt natürlich auch, dass es unmöglich ist, über Referenzen, die nicht mit einem kon- kreten statischen Typen deklariert sind, auf Objektfelder zuzugreifen.

Statische Felder

Objective-C bietet keine spezielle Syntax für statische Felder oder Klassenvariablen. Sie werden üblicherweise emuliert, indem in der Implementierungsdatei eine Variable deklariert wird, die nur in der Kompilationseinheit sichtbar ist.

#import "Klasse.h"

int anzahlObjekte;

@implementation Klasse @end

Da eine solche Variable nach außen unsichtbar ist, müssen Sie den Zugriff darauf über Methoden steuern.

Methoden

Methoden sind Funktionen, die mit einem Objekt oder einer Klasse verknüpft sind. Objekt- oder Instanzmethoden werden genutzt, um Operationen auszuführen, die auf den Feldern der jeweiligen In- stanz operieren. Sie können den von den Feldern bestimmten Zustand eines Objekts abfragen oder ändern. Klassenmethoden oder statische Methoden operieren auf der Klasse selbst und haben keinen Zugriff auf Felder eines spezifischen Objekts.

In Objective-C definieren Methoden die Nachrichten, die ein Objekt versteht. »Eine Methode aufrufen« und »einem Objekt eine Nach- richt senden« sind in Objective-C äquivalente Ausdrücke. Der Empfänger für eine Instanznachricht (Instanzmethode) ist eine Klasseninstanz, der Empfänger einer Klassennachricht (Klassen- methode) das Klassenobjekt, das die Klasse repräsentiert.

Welche Nachrichten ein Objekt versteht und wie ein Objekt auf eine Nachricht reagiert, wird in Objective-C nicht vollständig durch die Methoden bestimmt, die für eine Klasse deklariert und definiert sind. Objective-C bietet Mechanismen, die es ermöglichen, dyna- misch zur Laufzeit festzulegen, wie ein Objekt auf eine Nachricht reagiert. Diese werden im Kapitel 12, Messaging, beschrieben.

Anders als bei Feldern gibt es bei Methoden keine Möglichkeit, die Sichtbarkeit zu steuern. In der Schnittstelle deklarierte Methoden sind grundsätzlich öffentlich. Trotzdem kann man Methoden defi- nieren, die eine begrenzte Sichtbarkeit haben. Das beste und dazu geeignetste Mittel sind private Klassenerweiterungen (siehe Kapitel 5, Kategorien und Klassenerweiterungen, Abschnitt »Klassenerweiterun- gen« auf Seite 65).

Methoden deklarieren

Methoden werden üblicherweise in der Schnittstelle deklariert. Bei der Deklaration wird der Empfänger (die Klasse oder eine Instanz), der Rückgabetyp, der Methodenname und eine Folge eventueller Parameter angegeben. Die allgemeine Syntax hat folgende Form:

Art [(Ru¨ckgabetyp)] Namensteil[: (Paramtyp) Paramname] [Namensteil: (Paramtyp) Paramname] …

Art

Methoden können Instanz- oder Klassenmethoden sein. Ob der Empfänger ein Objekt oder die Klasse ist, wird angegeben, indem der Deklaration bei einer Instanzmethode ein --Zeichen vorangestellt wird und bei einer Klassenmethode ein +-Zeichen. Einer der beiden Indikatoren muss angegeben werden.

Rückgabetyp Gibt den Typ des Werts an, den eine Methode zurückliefert. Dieser wird gemäß der in Sprachen der C-Familie üblichen Syntax für Typumwandlungen in einer Klammer angegeben. Der Typ kann void sein (die Methode liefert nichts zurück) oder ein beliebiger von Objective-C unterstützter Datentyp bzw. ein Zeiger auf einen solchen. Wird kein Rückgabetyp angegeben, wird der Methode der Standardrückgabetyp id zugewiesen.

Namensteil Der Name einer Methode kann aus einem oder mehreren Namensteilen bestehen. Ein Namensteil muss vorhanden sein. Wenn eine Methode einen Parameter hat, wird dieser im Na- men durch einen Doppelpunkt eingeleitet. Weiteren Parame- tern gehen weitere mit weiteren Doppelpunkten abgeschlossene Namensteile voran.

Paramtyp Gibt den erwarteten Typ des Parameters an und steht wie der Rückgabetyp in Klammern.

Paramname Der Name des Parameters.

Konkret sieht das so aus:

Methoden ohne Parameter

Die allereinfachste Methodendeklaration könnte folgende Gestalt haben:

-ding;

Diese Deklaration besteht nur aus einer Empfängerangabe und einem Methodennamen. Sie deklariert eine Instanzmethode mit dem Namen ding, die keine Parameter erwartet und einen Wert des Typs id zurückliefert. Im Sinne der Transparenz sollten Sie auf die Angabe des Rückgabetyps nicht verzichten. Die äquivalente vollständige Deklaration sieht also so aus:

-(id) ding;

Methoden mit einem Parameter

Wenn eine Methode einen Parameter erwartet, folgt dieser nach einem Doppelpunkt auf den Methodennamen:

-(void) machWasMit: (id) ding;

Diese Deklaration deklariert eine Instanzmethode mit dem Namen machWasMit, die einen Parameter des Typs id erwartet und keinen Wert zurückliefert. Der Doppelpunkt ist Teil des Methodennamens (hier lautet dieser also machWasMit:). Der Parametertyp wird wie der Rückgabetyp in Klammern angegeben.

Methoden mit mehreren Parametern

Methodenname und Parameterliste sind in Objective-C keine sepa- raten Einheiten, sondern werden ineinander verwoben.

- (void) machWasMit: (id) ding und: (id) nochEinDing;

Der vollständige Methodenname lautet dann machWasMit:und:.

Es ist nur ein Namensteil notwendig, d. h., die Namensteile, aber nicht die Doppelpunkte vor weiteren Parametern, können wegge- lassen werden. Eine Methode, die zwei Parameter erwartet, könnte also auch so deklariert werden:

- (void) machWasMitDingUndDing: (id) ding1 : (id) ding2;

Der Methodenname wäre dann machWasMitDingUndDing::. Von die- ser Form wird abgeraten.

Da die Parameterliste Teil des Methodennamens ist, gibt es in Objective-C keine Methodenüberladung. Ein alternatives Konzept sind die sogenannten Methodenfamilien wie z. B. die Familie der init-Methoden. Eine Methodenfamilie umfasst Methoden mit ähn- lichem Funktionsbereich, deren Name durch das gleiche »Schlüssel- wort« eingeleitet werden.

Methodenimplementierung

Jeder Nachrichtendeklaration in der Schnittstelle sollte eine Nach- richtendefinition in der Implementierung entsprechen. Wenn der Compiler in der Implementierung keine kompatible Definition finden kann, warnt er, dass die Implementierung unvollständig ist.

Eine Definition beginnt mit einer Methodensignatur, die der in der Deklaration entspricht. Auf diese folgt ein Paar geschweifter Klam- mern, in dem der eigentliche Methodeninhalt definiert wird, zum Beispiel:

@interface Zahl : NSObject { @public int zahl;

}

-(int) malInt: (int) faktor; @end @implemention Zahl -(int) malInt: (int) faktor { return zahl * faktor;

}

@end

Der Compiler ist nicht immer dazu in der Lage, unvollständige oder falsche Implementierungen zu erkennen und zu melden. Abwei- chende Rückgabe- oder Parametertypen meldet er nur, wenn einer der Typen ein Objective-C-Typ (int, float usw.) ist. Beispielsweise würde folgende Implementierung zu zwei Warnungen führen, die Sie darauf aufmerksam machen, dass Rückgabetyp und Parameter- typ nicht der Deklaration entsprechen, weil für beides statt int long als Typ angegeben wird:

@implemention Zahl -(long) malInt: (long) faktor { return zahl * faktor;

}

@end

Wenn die Typen in Deklaration und Definition jeweils Objektzeiger sind, meldet der Compiler keinerlei Abweichungen. Folgende De- klaration

+(Zahl *) zahlAusString: (NSString *) text;

würde für den Compiler auch durch eine Definition wie

+(NSNumber *) zahlAusString: (NSArray *) text {

}

erfüllt. Da das zur Laufzeit im besten Fall zum unmittelbaren Programmabbruch, im schlimmsten Fall zu unvorhersagbaren Er- gebnissen führt, sollten Sie derartige Fehler vermeiden.

self

Der Methodeninhalt definiert die Operation, die eine Methode für die Klasse oder das Objekt stellt. Er kann auf alle Strukturen zugreifen, die im Code der Klasse bekannt sind (weil sie in der Kompilationseinheit selbst definiert werden oder mit #import in die Kompiliationseinheit importiert werden).

Ein entscheidender Unterschied zu C und den Methoden in anderen objektorientierten Programmiersprachen ist, dass Objective-C-Me- thodendefinitionen die Nachrichten definieren, die eine Klasse bzw. die Objekte einer Klasse verstehen, und keine einfachen, an ein Objekt oder eine Klasse gebundenen Funktionen darstellen. Wenn Sie dem aktuellen Objekt eine Nachricht senden, brauchen Sie dazu also einen Empfänger.

self ist ein spezieller Empfänger, der in allen Methodendeklaratio- nen definiert ist. Was self repräsentiert und welche Operationen mit self möglich sind, ist allerdings von der Art der Methode abhängig. In Code, der ARC-basiert ist (siehe Kapitel 10, Speicher- verwaltung), gelten folgende Regeln:

• In einer Klassenmethode (im Beispiel unten +zahlMitInt:) ist self ein konstanter Wert des Typs Class, der die Klasse selbst repräsentiert.

• In einer gewöhnlichen Instanzmethode (im Beispiel unten z. B. -alsInt) ist self ein konstanter Zeiger auf einen Wert des Klassentyps, mit dem interagiert, der aber nicht geändert werden kann.

• In einer Instanzmethode der init-Familie (im Beispiel unten -initMitInt:) ist self ein Zeiger auf einen Wert des Klassen- typs, der geändert werden kann.

Schauen wir uns zur Illustration die Implementierung der folgenden Schnittstelle an:

@interface Zahl : NSObject { int zahl;

}

+(id) zahlMitInt: (int) wert; -(id) initMitInt: (int) wert; -(void) malInt: (int) faktor; -(void) malZahl: (Zahl *) andereZahl; -(void) zumQuadrat; -(int) alsInt; @end

Da die Ivar zahl in allen Instanzmethoden zugreifbar ist, können wir -alsInt einfach implementieren, indem wir den Wert von zahl zurückliefern:

-(int) alsInt { return zahl;

}

Der Zugriff auf -zahl kann wie oben unqualifiziert oder mit self- qualifiziert erfolgen, da die Ivar auf dem aktuellen Objekt gelesen werden soll:

-(int) alsInt { return self->zahl;

}

Beide Formen sind im Prinzip äquivalent. Die Qualifizierung mit self verhindert, dass versehentlich auf eine lokale Variable zuge-

griffen wird, die das Feld verdeckt. Der Compiler warnt, wenn eine lokale Variable erstellt wird, die ein Feld verdeckt.

Ebenso können wir -malZahl: implementieren, indem wir den Wert von zahl mit dem Wert des Arguments multiplizieren:

-(void) malInt: (int) faktor { self->zahl *= faktor;

}

Aber wir könnten -zumQuadrat nicht wie z. B. in Java implemen- tieren, indem wir unqualifiziert eine malInt()-»Methode« aufrufen. -malInt ist eine Nachricht und muss an einen explizit anzugeben- den Empfänger gesendet werden. Im Klassencode wird die aktuelle Instanz mit self angegeben:

-(void) zumQuadrat { [self malInt: self->zahl];

}

Da zumQuadrat eine gewöhnliche Instanzmethode ist, kann self nur als Empfänger für Nachrichten bzw. als Objektzeiger zur Inter- aktion mit Ivars genutzt werden.

In Methoden der init-Familie (mehr Informationen dazu finden Sie in Kapitel 9, Objektlebenszyklus, im Abschnitt »Objektinitialisie- rung« auf Seite 89) sind darüber hinaus auch Zuweisungen an self erlaubt:

-(id) initMitInt: (int) wert { self = [self init]; self->zahl = wert; return self;

}

Anders als z. B. this in Java ist self auch in Klassenmethoden definiert. In Klassenmethoden repräsentiert self allerdings nicht die Instanz, sondern das Class-Objekt, das der Compiler auf Basis der Klassendefinition bereitstellt. Über das Class-Objekt können Sie Informationen zur Klasse erhalten und neue Klasseninstanzen er- stellen. Aber da es keine Instanz ist, können Sie auf ihm nicht auf Instanzvariablen oder -methoden zugreifen, sondern nur auf Klas- senmethoden bzw. auf Funktionen, Variablen usw., die in der Kompilationseinheit deklariert sind.

Diese self-Referenz können Sie in Klassenmethoden beispielsweise nutzen, um sicherzustellen, dass stets ein Objekt mit dem richtigen Laufzeittyp erstellt und geliefert wird:

+(id) zahlMitInt: (int)wert { return [[self alloc] initMitZahl: wert];

}

Wird diese Klassennachricht einer UngeradeZahl-Unterklasse von Zahl gesendet, wird eine UngeradeZahl-Instanz erstellt, da self das Klassenobjekt repräsentiert, das Empfänger der Nachricht ist. Wäre hier fest der Typ der Klasse vorgegeben worden, in der die Methode definiert wird (Zahl), würde ein Zahl-Objekt erstellt.

Methoden aufrufen

In Objective-C werden die Begriffe Methode aufrufen und einem Objekt eine Nachricht senden häufig synonym verwendet, obwohl der zweite Begriff eigentlich weiter ist. In »statischen« Programmier- sprachen bewirkt der Aufruf einer Methode (oder Funktion), dass vorab festgelegter Code ausgeführt wird. Der Aufruf wird statisch, d. h. bei der Kompilierung aufgelöst. Objective-C hingegen ist eine »dynamische« Programmiersprache, in der Aufrufe zur Laufzeit aufgelöst werden. Objective-C-Objekte empfangen Nachrichten und entscheiden zur Laufzeit, welcher Code aufgerufen wird. Das kann der Code einer vorab definierten Methode sein, könnte aber auch Code sein, der erst zur Laufzeit bestimmt wird. Die definierten Methoden stellen also nicht notwendigerweise alle Nachrichten dar, die ein Objekt kennt (siehe Kapitel 12, Messaging).

Da Sie das nun wissen: In der Regel definieren Sie die Nachrichten, die ein Objekt versteht, indem Sie Methoden als Funktionen im- plementieren, die an ein Objekt gebunden sind. Und wenn Sie einem Objekt eine Nachricht senden, führt das in der Regel dazu, dass eine Methode aufgerufen wird, die auf diese Weise implemen- tiert wurde. Aber weder das eine noch das andere muss der Fall sein.

Wie in Kapitel 2, Syntax, bereits erläutert, wird eine Methode aufgerufen, indem einem Empfänger eine Nachricht gesendet wird:

[Empfa¨nger Nachricht]

Nachfolgend finden Sie zur Auffrischung noch ein paar Beispiele dafür, wie Sie einige der Methoden aufrufen, die wir im Abschnitt »Methoden deklarieren« auf Seite 39 deklariert haben:

// Eine Nachricht ohne Parameter [ding ding]; // Eine Nachricht mit einem erwarteten Parameter [zahl zahlMalInt: 3]; // Eine Klassennachricht mit mehreren Parametern [Person initMitName: @"Hugo" alter: 33 augenfarbe: @"Gru¨n"];

Die [ding ding]-Zeile wird Ihnen vielleicht seltsam erscheinen. In Objektive-C gibt es unabhängige Namensbereiche für Instanzvaria- blen und Nachrichten. Eine Instanzvariable, die den gleichen Na- men wie eine Nachricht hat, führt deswegen nicht zu Konflikten.

Varargs-Methoden

Objective-C nutzt die gleiche Syntax und Infrastruktur für Metho- den mit Unterstützung für eine beliebige Anzahl an Argumenten wie C. Die Deklaration in der Schnittstellendatei hat folgende Gestalt.

-(void) verarbeiteDinge: (id) ding1,

;

zeigt an, dass hier ein Varargs-Parameter folgt.

Einem Varargs-Parameter muss immer mindestens ein benannter Parameter vorangehen, der in der Regel (aber nicht notwendiger-

Der Indikator ,

weise) wie oben das erste Element in der Liste der erwarteten Argumente repräsentiert.

Zur Implementierung von Varargs-Methoden werden in Objecti-

ve-C der Typ va_list und die Makros va_start, va_args und va_end

genutzt, die alle in der bibliothek definiert sind.

va_start initialisiert eine va_list-Struktur in Bezug auf den letzten benannten Parameter in der Parameterliste. va_args ruft das nächste Argument mit dem angegebenen Typ aus der Liste ab. va_end schließt die angegebene Argumentliste, wenn sie nicht mehr benö- tigt wird.

der C-Standard-

stdargs.h -Headerdatei

Möchten Sie einen Varargs-Parameter nutzen, müssen Sie irgendwie in Erfahrung bringen können, wie lang die Liste ist bzw. wann sie zu Ende ist. In der Cocoa-Bibliothek werden häufig nil-terminierte Listen genutzt. Eine solche könnten Sie folgendermaßen implemen- tieren:

-(void) machWasMit: (id) ding1, va_list args; va_start(args, ding1); // Was mit ding1 machen id ding; while ((ding = va_arg(args, id))) {

//Was mit den restlichen u¨bergebenen Argumenten machen

}

va_end(args);

}

{

Der Header für die Varargs-Infrastruktur muss gewöhnlich nicht eigens importiert werden, da er bereits von den wahrscheinlich zum Einsatz kommenden Cocoa-Headern importiert wird.

Eine Varargs-Methode wie diese rufen Sie auf, indem Sie Varargs- Argumente durch Kommata getrennt angeben und das Ende der Argumentliste mit nil anzeigen:

[machWasMit: ding1, ding2, ding3, nil];

Varargs-Parameterlisten müssen nicht notwendigerweise nil-termi- niert sein. Es gibt z. B. Framework-Methoden wie stringWithFor- mat, die kein nil am Ende der Argumentliste erwarten:

[NSString stringWithFormat: @"Zahl %i und Wort %@", 1, @"Eins"]

Bei dieser Form gibt es nichts, was das Ende der Argumentliste anzeigt. Der Compiler ist aber in der Regel dazu in der Lage, Sie zu warnen, wenn Anzahl und Typ der Parameter falsch sein könnten.

Eigenschaften

Eigenschaften bzw. Properties kapseln und schützen den Zugriff auf die Daten von Objekten. Implementiert werden sie in Objective-C wie in anderen Programmiersprachen über ein privates Feld und zwei Zugriffsmethoden, eine zum Setzen und eine zum Abrufen des

Feldwerts. In Objective-C haben diese Zugriffsmethoden konventi- onsgemäß folgende Signaturen:

-( Typ ) Name (Getter) -(void) setName : ( Typ ) Variable (Setter).

(Getter) -(void) set Name : ( Typ ) Variable (Setter). In Objective-C sollten Sie anders als

In Objective-C sollten Sie anders als bei vielen anderen Programmiersprachen üblich den Na- men des Getters nicht mit get beginnen. Dieses Präfix zeigt gemäß den Cocoa-Konventionen Methoden an, die Werte über Referenzen bear- beiten. Da die Namen von Feldern und Metho- den in Objective-C separat verwaltet werden, kann der Getter den gleichen Namen wie das Hintergrundfeld haben. Theoretisch könnte auch das set in Settern weggelassen werden, da der Doppelpunkt, der das Argument anzeigt, Teil des Namens ist – aber diese Verwendung gehört nicht zu den gängigen Objective-C-Kon- ventionen.

Manuell würde man eine anzahl-Eigenschaften also auf folgende Weise deklarieren und implementieren:

@interface Dinge : NSObject { @private int _anzahl;

}

-(void) setAnzahl: (int) anz;

-(int) anzahl;

@end

@implementation Dinge -(void) setAnzahl: (int) anz { _anzahl = anz;

}

-(int) anzahl { return _anzahl;

}

@end

Eigenschaften deklarieren

Objective-C vereinfacht den Einsatz von Eigenschaften durch eine integrierte Syntax für ihre Deklaration und Definition. Sie können mit der @property-Direktive in der Schnittstelle deklariert werden. Diese wird folgendermaßen verwendet:

@property [(Attribut, …)] Typ Eigenschaft; Attribut

Die Attribute der Eigenschaftsdeklaration bestimmen Aussehen und Verhalten der Eigenschaft. Eine vollständige Liste finden Sie in Kapitel 10, Speicherverwaltung, im Abschnitt »Eigen- schaftsattribute« auf Seite 106.

Typ

Gibt den Datentyp der Eigenschaft an.

Eigenschaft

Der Name der Eigenschaft.

In der einfachsten Verwendung hat die @property-Direktive fol- gende Form:

@interface Dinge : NSObject {

}

@property int anzahl; @end

Diese @property-Deklaration ist der manuellen Deklaration im letz- ten Abschnitt mit Zugriffsmethoden mit den Standardsignaturen für Getter und Setter äquivalent.

Alle Eigenschaften werden mit einer eigenen, mit einem Semikolon abgeschlossenen @property-Direktive deklariert.

In der Schnittstelle deklarierte Eigenschaften sind wie Methoden grundsätzlich öffentlich sichtbar. Nur die Les- und Schreibbarkeit kann über Eigenschaftsattribute gesteuert werden. Private Eigenschaf- ten können wie private Methoden mithilfe von Klassenerweiterungen (siehe Kapitel 5, Kategorien und Klassenerweiterungen, Abschnitt »Klassenerweiterungen« auf Seite 65) erstellt werden.

Synthetisierung deklarierter Eigenschaften

Deklarierte Eigenschaften können explizit synthetisiert oder manu- ell implementiert werden. Gibt es weder eine explizite Synthetisie- rung noch eine manuelle Implementierung, werden deklarierte Eigenschaften seit Xcode 4.4 automatisch synthetisiert.

Automatische Synthetisierung

Automatische Synthetisierung heißt, dass der Compiler für eine @property-Deklaration in der Schnittstellendatei das Hintergrundfeld und die erforderlichen Zugriffsmethoden in der Implementierungs- datei erzeugt. Für eine Eigenschaft, die mit der Direktive @property (Typ) eigenschaft deklariert wurde, erzeugt der Compiler:

• ein privates Hintergrundfeld mit dem Namen _eigenschaft

• einen Getter mit der Signatur - (Typ) eigenschaft

• einen Setter mit der Signatur - (void) setEigenschaft: (Typ) wert

Anders gesagt: Wenn Sie eine Eigenschaft nicht weiter konfigurie- ren müssen, reicht es aus, sie in der Schnittstelle mit @property zu deklarieren. Die erforderlichen Einrichtungen zum Arbeiten mit der Eigenschaft erhalten Sie dann automatisch.

Bei der oben deklarierten Eigenschaft anzahl hieße das, dass ein int-Feld des Namens _anzahl und zwei Methoden mit den Signatu-

ren -(int) anzahl (Getter) und -(void) setAnzahl: (int) wert

definiert sind und genutzt werden können.

Explizite Synthetisierung

Zur expliziten Synthetisierung können Sie in der Implementierungs- datei die @synthesize-Direktive nutzen. Eine explizite Synthetisie- rung ist bei neueren Werkzeugversionen in der Regel überflüssig. @synthesize wird folgendermaßen verwendet:

@synthesize eigenschaft[=feld],

;

@synthesize weist den Compiler an, Zugriffsmethoden mit den Namen eigenschaft und setEigenschaft: sowie ein Hintergrund-

feld mit dem Namen eigenschaft oder, wenn =feld angegeben ist, mit dem Namen feld zu »synthetisieren«, falls in der Schnittstelle kein entsprechendes Feld deklariert wurde.

In der einfachsten Form hat eine @synthesize-Direktive folgende Form:

@synthesize anzahl;

Bei dieser expliziten Synthetisierung werden wie bei der automati- schen Synthetisierung ein anzahl-Getter und ein setAnzahl:-Setter generiert. Anders als bei einer automatischen Synthetisierung wird dem Namen des Hintergrundfelds kein Unterstrich vorangestellt. Der Name ist anzahl, nicht _anzahl. Das ist etwas, das Sie bei der Umstellung von einer expliziten auf eine automatische Synthetisie- rung (oder umgekehrt) beachten müssen.

Bei der Form

@synthesize anzahl=_menge;

würde statt eines anzahl-Hintergrundfelds ein _menge-Hintergrund- feld synthetisiert, falls im Feldabschnitt der Schnittstelle kein ent- sprechendes Feld deklariert ist.

der Schnittstelle kein ent- sprechendes Feld deklariert ist. Beachten Sie, dass synthetisierte Hintergrund- felder mit

Beachten Sie, dass synthetisierte Hintergrund- felder mit deklarierten Ivars in Konflikt treten können.

Deklarierte Eigenschaften implementieren

Sie können Zugriffsmethoden auch manuell implementieren. Das ist z. B. sinnvoll, wenn Sie mehr Funktionalität benötigen, als die Standard-Getter und -Setter bieten, zum Beispiel.:

@interface Dinge :NSObject {

}

@property (nonatomic) int anzahl; @end

@implementation Dinge

-(void) setAnzahl: (int) wert {

= wert;

if (wert > 0) _anzahl

}

@end

Der Compiler erkennt automatisch, dass -setAnzahl: ein explizit definierter Setter für die deklarierte Eigenschaft anzahl sein soll, und synthetisiert nur noch den Getter und das Hintergrundfeld.

Wenn Sie synthetisierte und selbst definierte Zugriffsmethoden kom- binieren, müssen Sie die Eigenschaft mit dem Attribut nonatomic versehen. Eigenschaften sind standardmäßig atomar, und die syn- thetisierten Methoden werden entsprechend implementiert. Imple- mentieren Sie eine der Zugriffsmethoden manuell, beschwert sich der Compiler, weil Sie atomare und nicht atomare Zugriffsmethoden nicht kombinieren können. Alternativ können Sie beide Zugriffs- methoden manuell definieren.

@dynamic

Die Direktive @dynamic garantiert dem Compiler, dass die Imple- mentierung einer deklarierten Eigenschaft zur Laufzeit zur Ver- fügung stehen wird. Sie bewirkt, dass er nichts in Bezug auf die Eigenschaft unternimmt. Er generiert keine Zugriffsmethoden und Hintergrundfelder bzw. meldet keine Warnungen oder Fehler.

@dynamic wird wie @synthesize verwendet:

@dynamic Eigenschaft;

Wenn Sie @dynamic nutzen, müssen Sie dafür sorgen, dass Objekte zur Laufzeit auf die entsprechenden Nachrichten reagieren, z. B. indem Sie die Nachricht über den Weiterleitungsmechanismus von einem anderen Objekt verarbeiten lassen (siehe Kapitel 12, Messa- ging).

Eigenschaftszugriff

Eigenschaften sind ein Konstrukt, dessen öffentliche Schnittstelle zwei Methoden sind. Diese können Sie jederzeit explizit nutzen:

Dinge *dinge = [Dinge new]; [dinge setAnzahl: 5]; int zahl = [dinge anzahl];

Neben der Nachrichtensyntax unterstützt Objective-C 2.0 die aus anderen objektorientierten Programmiersprachen bekannte Punkt- notation für den Zugriff auf Eigenschaften. Das heißt, bei statisch typisierten Referenzen kann stattdessen folgender Code genutzt werden:

Dinge *dinge = [Dinge new]; dinge.anzahl = 5; int anzahl = dinge.anzahl;

Vom Compiler wird dieser Code zu den gleichen Aufrufen aufgelöst wie der vorangehende nachrichtenbasierte Code. Die Punktnota- tion bietet den Vorteil, dass der Compiler statisch prüfen kann, ob das Objekt die entsprechende Nachricht unterstützt. Außerdem lassen sich viele Operationen kompakter formulieren, da Zuweisun- gen ihre C-Semantik bewahren, d. h. Ausdrücke sind, die zum Wert der Eigenschaft ausgewertet werden. Der Code ließe sich also noch weiter vereinfachen:

Dinge *dinge = [Dinge new]; int anzahl = dinge.anzahl = 5;

Außerdem sind neben der einfachen Zuweisungsanweisung auch die zusammengesetzten Zuweisungsanweisungen definiert. Das be- deutet,

[dinge setAnzahl: [dinge anzahl] + 5];

kann mithilfe der Punktnotation zu

dinge.anzahl += 5;

verkürzt werden.

Komplexe Anweisungsfolgen wie

[dinge setAnzahl: 5]; [dinge setAnzahl: [dinge anzahl] * 5]; int anzahl = [dinge anzahl];

könnten zu Folgendem verkürzt werden:

int anzahl = dingeAnzahl *= dingeAnzahl = 5;

Beachten Sie, dass die Punktnotation nur bei Objektreferenzen genutzt werden kann, die statisch mit dem

Beachten Sie, dass die Punktnotation nur bei Objektreferenzen genutzt werden kann, die statisch mit dem Typ (oder einer Unterklasse des Typs) deklariert sind, für den die Eigen- schaft definiert wurde. Andernfalls (d. h. bei einer Referenz des Typs id oder einer Referenz des Typs einer Oberklasse des Typs, für den die Eigenschaft definiert ist) schlägt die stati- sche Typprüfung des Compilers und damit die Kompilierung fehl, obwohl die dynamische Nachrichtenauflösung zur Laufzeit die ent- sprechenden Methoden finden würde.

Die Punktnotation kann nicht nur verwendet werden, wenn die Eigenschaft explizit mit der @property-Direktive deklariert wurde, sondern auch, wenn die Eigenschaft vollständig manuell mit anzahl- und setAnzahl:-Nachrichten implementiert wurde. Da der Compiler die Punktnotation in die entsprechenden Nachrichten an das Objekt übersetzt, könnte sie theoretisch auch für Nicht-Eigen- schaftsnachrichten verwendet werden. Von dieser Verwendung wird jedoch abgeraten.

Eigenschaftsattribute

Die @property-Direktive unterstützt eine Reihe von Attributen zur Steuerung des Zugriffs, der Setter-Operationsweise, der Namen der Zugriffsmethoden und zur Threading-Unterstützung. Diese Attri- bute werden in Klammern hinter der @property-Direktive angege- ben. Mehrere Attribute werden durch Kommata getrennt:

@property (Attribut1, Attribut2,

) Typ Eigenschaft;

Geben Sie hinter @property keine Attribute an, werden die Stan- dardattribute genutzt. Eine attributlose Eigenschaftsdeklaration (@propertyTyp Eigenschaft) entspricht unter ARC der Deklaration

Eigenschaften sind

standardmäßig atomar, d. h., Eigenschaftsoperationen werden als

Einheit abgewickelt (siehe @nonatomic weiter unten).

@property

(readwrite,strong)

Eigenschaft.

Setter- und Getter-Namen definieren

Die folgenden Attribute können Sie verwenden, um die Namen der zu nutzenden Zugriffsmethoden manuell festzulegen.

setter= Name

Zeigt an, dass die Setter-Methode den Namen Name hat. Wenn in der Implementierungsdatei keine entsprechende Methode vorhanden ist, wird sie synthetisiert (es sei denn, die Direktive @dynamic wird verwendet).

getter= Name

Zeigt an, dass die Getter-Methode den Namen Name hat. Wenn in der Implementierungsdatei keine entsprechende Methode vorhanden ist, wird sie synthetisiert (es sei denn, die Direktive @dynamic wird verwendet).

Die Attribute in den nachfolgenden Bereichen schließen sich gegen- seitig aus, d. h., es kann jeweils nur ein Attribut aus einem Bereich angegeben werden.

Zugriffsteuerung

Die folgenden Attribute steuern den Zugriff auf eine Eigenschaft.

readwrite

Zeigt an, dass eine Eigenschaft lesbar und schreibbar ist. In der Implementierungsdatei müssen entweder explizit eine Setter- und eine Getter-Methode definiert werden, oder Setter- und Getter-Methoden werden synthetisiert (der Standardwert).

readonly

Zeigt an, dass eine Eigenschaft schreibgeschützt ist. In der Implementierungsdatei muss nur eine Setter-Methode definiert sein, bzw. es wird nur eine Setter-Methode synthetisiert.

Setter-Operation

Alle nachfolgenden Attribute steuern die Zuweisung an die Eigen- schaft und die Speicherverwaltung. retain und assign kommen bei Referenzzählung zum Einsatz, strong und weak bei Automatic Refe- rence Counting. Die Speicherverwaltung und die Bedeutung der

Eigenschaftsattribute wird in Kapitel 10, Speicherverwaltung, genauer erläutert.

assign

Einfache Zuweisung. Wird üblicherweise für skalare Werte oder (bei Referenzzählung) für Objekte genutzt, die nicht in der Klasse erstellt wurden (der Standard).

retain

Bei der Zuweisung wird dem neuen Wert die retain-Nachricht gesendet, dem alten die release-Nachricht.

strong

Eine starke Referenz. Solange es eine starke Referenz gibt, wird das Objekt nicht dealloziert. Entspricht retain und ist der Standard bei Automatic Reference Counting.

weak

Eine schwache Referenz. Wird unter ARC genutzt und ent- spricht assign, sorgt aber dafür, dass die Referenz auf nil gesetzt wird, wenn das Objekt dealloziert wird.

copy

Für die Zuweisung wird eine Kopie des übergebenen Objekts verwendet. Die Kopie wird mit der copy-Methode erstellt, d. h., dieses Attribut kann nur für Objekte eingesetzt werden, die das NSCopying-Protokoll unterstützen. Dem alten Wert wird eine release-Nachricht gesendet.

Atomarita¨ t

nonatomic

Zeigt an, dass die Zugriffsmethoden nicht atomar sind. Stan- dardmäßig sind Zugriffsmethoden atomar, ein entsprechendes Attribut gibt es jedoch nicht.

Vererbung und Polymorphie

Die Klassen einer Programmierschnittstelle stehen in einem hierar- chischen Verhältnis, das dem der Familien, Gattungen und Arten naturwissenschaftlicher Taxonomien vergleichbar ist. Wenn Sie

sich eine solche Klassenhierarchie wie einen Stammbaum vorstellen, bei dem die Wurzel oben ist, finden Sie unten engere oder spezi- fischere Klassen, die mit zunehmender Genauigkeit die spezifischen Eigenschaften einer immer kleiner werdenden Gruppe von Objek- ten beschreiben. Oben finden Sie weitere oder allgemeinere Klassen, die eine nach oben hin immer größer werdende Menge von Objek- ten unter einer stets schrumpfenden Anzahl von Gemeinsamkeiten beschreiben. An der Spitze dieser Hierarchie steht die Basisklasse (siehe Kapitel 8, NSObject), die die grundlegenden Gemeinsamkei- ten aller Objekte beschreibt, die Exemplare der ihr untergeordneten spezielleren Klassen sind. Alle anderen Klassen sind erweiternde oder abgeleitete Klassen, die jeweils den Begriff ihrer Elternklassen um die für ihre Exemplare spezifischen Eigenschaften erweitern.

Mit Ausnahme der Basisklasse sind alle Klassen einer Klassenhie- rarchie abgeleitete Klassen. Ein Objekt, das eine Instanz einer abge- leiteten Klasse B ist, besitzt neben den speziellen Eigenschaften und Verhalten von Objekten der Klasse B alle speziellen und geerbten Eigenschaften und Verhalten von Objekten der Klasse A, wenn A die Elternklasse von B ist.

Schauen wir uns zur Illustration zwei Klassen A und B an, die durch folgende Schnittstellen beschrieben werden (wie im Abschnitt »Schnittstellendeklaration« auf Seite 34 beschrieben, wird die El- ternklasse in der Schnittstellendeklaration mit einem Doppelpunkt nach dem Klassennamen angegeben):

@interface A : NSObject @property int a; -(int) zumQuadrat; @end

@interface

@property int b;

-(int) aMalB;

@end

B

:

A

Die Klasse A ist von NSObject, der Basisklasse der Cocoa-Klassen- hierarchie, abgeleitet. In Objective-C muss die Basisklasse immer explizit angegeben werden. Wird in einer Klassendeklaration keine Elternklasse angegeben, wird eine neue Basisklasse deklariert. Die Klasse B ist von A abgeleitet.

Vererbung

Vererbung bedeutet, dass eine abgeleitete Klasse alles erbt, was ihre Elternklassen definieren. Die Deklarationen oben vorausgesetzt, heißt das, dass B alle Eigenschaften und Verhalten von A und NSObject erbt, da A die Basisklasse NSObject erweitert und B seinerseits A erweitert. Konkret bedeutet es, dass im Code von B alle nicht privaten Ivars, Methoden und Eigenschaften von A (und NSObject) sichtbar sind. In B kann auf die geerbte Eigenschaft a also zugegriffen wie auf die eigene Eigenschaft b. Die Implementierung von B könnte z. B. so aussehen:

@implementation B -(int) aMalB { return self.b*self.a;

}

@end

Für einen Nutzer von Objekten des Typs B gibt es keinen Unter- schied zwischen den eigenen und den geerbten Eigenschaften und Methoden, zum Beispiel:

B *bObjekt = [B new]; // new wurde u¨ber A von NSObject geerbt if (!(bObjekt.a == bObjekt.b && [bObjekt zumQuadrat] == [bObjekt bMalA])) { NSLog(@"Große Katastrophe: 0^2 != 0*0");

}

Vererbung ermöglicht es, die Eigenschaften, die eine Gruppe von Klassen gemeinsam haben, in einer gemeinsamen Elternklasse zu- sammenzufassen. Beachten Sie an diesem Punkt, dass Klassen in Objective-C grundsätzlich konkret sind. Es bietet keine Einrichtung zur Definition abstrakter Klassen.

In Objective-C können Klassen anders als z. B. in C++ nur eine Elternklasse haben. Protokolle (siehe Kapitel 6, Protokolle) bieten eine zusätzliche Möglichkeit Gemeinsamkeiten in der Schnittstelle zu beschreiben.

Eigenschaften und Verhalten u¨ berschreiben

Wenn eine abgeleitete Klasse einen Aspekt des Verhaltens einer Elternklasse ändern will, kann sie die entsprechende Definition in einer beliebigen Elternklasse durch eine eigene Definition über-

schreiben. Die Neudefinition in der abgeleiteten Klasse verdeckt dann nach außen hin die Definition in der Elternklasse.

Zum Beispiel könnte B folgendermaßen die -zumQuadrat-Methode von A neu definieren:

@implementation B -(int) zumQuadrat { return pow(self.b, 2);

}

@end

Die ursprüngliche -zumQuadrat-Methode von A ist nun auf B-Objek- ten nicht mehr verfügbar.

Im Unterschied zu Sprachen wie Java ist es in Objective-C auch möglich und üblich, Klassenmethoden zu überschreiben:

super

In einer abgeleiteten Klasse kann auf Elternklassenelemente über den speziellen Empfänger super zugegriffen werden. super sorgt dafür, dass mit der Suche nach einer Nachrichtendefinition bei der Elternklasse begonnen wird. Zum Beispiel könnte man folgender- maßen mit super in der Implementierung von B auf die zumQuadrat- Definition von A zugreifen:

-(int) aQuadrat { return [super zumQuadrat];

}

@end

Ansonsten verhält sich super fast genau so wie self (siehe »self« auf Seite 42). Der einzige Unterschied ist, dass auch in init-Methoden keine Zuweisung an super erlaubt ist.

in init -Methoden keine Zuweisung an super erlaubt ist. Beachten Sie, dass super und die superclass

Beachten Sie, dass super und die superclass- Nachricht unterschiedliche Funktionen haben. super sorgt dafür, dass die Methodenauflö- sung bei der Oberklasse ansetzt, verändert die Klasse von self aber nicht. superclass hin- gegen liefert das Klassenobjekt, das die Ober- klasse repräsentiert.

Polymorphie

Der Begriff Polymorphie verweist darauf, dass eine Referenz des Typs T Objekte aller abgeleiteten Klassen X, Y, Z usw. festhalten kann und dass ein Objekt vom Typ T über Referenzen aller Eltern- klassen A, B, C usw. festgehalten werden kann.

Wenn ein Objekt den Typ B hat, der von einem Typ A abgeleitet ist, kann es als Objekt des Typs B oder als Objekt des Typs A behandelt werden. Wird es als Objekt des Typs A behandelt, werden von ihm nur Dinge verlangt, die Objekte des Typs A beherrschen. Da B alle Eigenschaften und Verhalten von A erbt, ist garantiert, dass ein Objekt des Typs B allen Anforderungen genügt, die an ein Objekt des Typs A gestellt werden können:

A *bObjekt = [B new]; bObjekt.a = 3; NSLog(@"%i", [bObjekt zumQuadrat]);

Nützlich wird das, wenn man Objekte unterschiedlichen, aber ver- wandten Typs in Bezug auf bestimmte Operationen gemeinsam behandeln kann. Stellen Sie sich z. B. vor, Sie müssten eine Tankwart- Klasse für eine Verkehrssimulation schreiben. Sie würde nur interes- sieren, dass die Objekte, mit denen Sie zu tun haben, die -(void) tankFuellen: (float) liter-Nachricht verstehen. Ob ein Fahrzeug ein Ferrari, ein Reisebus, eine Harley oder selbst ein Ausflugsdampfer ist, ist für einen Tankwart vollkommen irrelevant. Die betanken-Me- thode von Tankwart könnte also folgende Gestalt haben:

-(void) betanken: (Kfz *)kfz { while (kfz.benzin < kfz.tankvolumen) { [kfz tankFuellen: 1];

}

Und so könnte sie verwendet werden:

Ferrari *roterBlitz = [Ferrari new]; Bus *linie1 = [Bus new]; [tankwart betanken: roterBlitz]; [tankwart betanken: linie1];

In Objective-C ist diese Vielförmigkeit nichts Besonderes, für das von der Sprache spezielle Einrichtungen bereitgestellt werden. Ob- jektreferenzen sind einfach mit Typinformationen versehene Zeiger, die für alle Objekte gleich sein. Sie können deswegen von Haus aus

Werte beliebigen Typs referenzieren. Ein Zeiger auf einen Objekttyp kann beispielsweise in einen Zeiger auf jeden beliebigen anderen Objekttyp umgewandelt werden. Der Compiler hätte gegen Folgen- des in Objective-C kein Einwände:

[tankwart betanken: (Kfz *)@"Auto"];

Die Typumwandlung selbst würde auch zur Laufzeit noch gelingen, und ein Fehler würde erst auftauchen, wenn in -betanken auf dem NSString-Objekt Operationen aufgerufen werden, die von NSString- Objekten nicht unterstützt werden (siehe Abschnitt »Typumwand- lungen« unten).

Objective-C unterstützt deswegen auch das sogenannte Duck Ty- ping, das man üblicherweise eher mit Skriptsprachen ohne statische Typprüfung verbindet.

Typumwandlungen

Vererbungsbeziehungen spielen bei der Umwandlung von Zeigern auf Objekttypen nur insofern eine Rolle, als dass implizite Upcasts (Umwandlungen in einen Elterntyp) erkannt und akzeptiert werden. Ansonsten führen implizite Typumwandlungen immer zu einer Com- pilerwarnung, während alle expliziten Typumwandlung unkommen- tiert akzeptiert werden. Beispielsweise führt folgende Zuweisung bei der Kompilierung nur zu einer Warnung, obwohl die Klassen NSArray und NSString nicht in einem Vererbungsverhältnis stehen:

NSArray *text = @"Riskant";

Mit einem expliziten Cast können wir sogar diese Warnung unter- drücken:

NSArray *text = (NSArray *)@"Riskant";

Bei einer Sprache wie Java würde die implizite wie die explizite Typumwandlung zu einem Kompilierungsfehler führen.

Zur Laufzeit gelingen Typumwandlungen, explizite wie implizite, immer (so etwas wie eine ClassCastException kennt Objective-C nicht). Dass Zeiger- und Objekttyp inkompatibel sind, fällt erst auf, wenn zur Laufzeit auf dem Zeiger eine Operation angefordert wird, die das Objekt nicht unterstützt.

KAPITEL 5

Kategorien und Klassenerweiterungen

Kategorien bieten eine Möglichkeit, bestehenden Klassen, auch eingebauten Framework-Klassen oder anderen Klassen, über deren Quellcode Sie nicht verfügen, nachträglich Methoden hinzuzufü- gen. Sie ermöglichen Ihnen also, die Funktionalität einer Klasse nachträglich zu erweitern – eine Technik, die unter dem Namen Monkey Patching bekannt ist. Alle Methoden, die Sie in einer Kategorie auf einer Klasse definieren, werden zu einem Teil der ursprünglichen Klasse.

Die Deklaration einer Kategorie wird angezeigt, indem bei der Schnittstellendeklaration hinter dem Klassennamen in Klammern der Name der Kategorie angegeben wird:

#import "Klasse.h" @interface Klasse (Kategorie) // Methodendeklarationen @end

Kategorien können nur Methodendeklarationen enthalten, keine Felder – es gibt also keinen Abschnitt mit Felddeklarationen. Das heißt auch, dass Sie in Kategoriedeklarationen zwar mit @property neue Eigenschaften deklarieren, diese in der Implementierungsdatei aber nicht mit @synthesize synthetisieren können – Getter und Setter müssen manuell definiert werden und können nur die Ein- richtungen, d. h. Felder nutzen, die von der ursprünglichen Klas- sendeklaration bereitgestellt werden. Tun Sie das nicht, meldet der Compiler nur eine Warnung, keinen Fehler. Kategorienamen haben

einen eigenen Namensraum. Sie könnten beispielsweise also eine Kategorie mit dem gleichen Namen wie die Klasse definieren.

Wenn Sie die Kategorie im gleichen Header wie die Klasse selbst deklarieren, sind die darin deklarierten Methoden vollwertige Klas- senmethoden und können genau so eingesetzt und aufgebaut wer- den wie die Methoden der Klasse selbst. Kategoriemethoden haben Zugriff auf alle deklarierten Einrichtungen der Klasse und können beispielsweise sogar private Felder lesen.

Deklarieren Sie die Kategorie in einem eigenen Header, müssen Sie in diesen den Header für die Klassendeklaration einschließen. Au- ßerdem müssen Sie in Code, der die Kategorie nutzt, zusätzlich zum Klassenheader den Kategorieheader einbinden. Tun Sie das nicht, meldet der Compiler einen Fehler, wenn Sie ARC (siehe Kapitel 10, Speicherverwaltung) nutzen, oder eine Warnung, wenn Sie ARC nicht nutzen. Ansonsten unterscheiden sich die entsprechenden Methoden in keiner Weise von Kategoriemethoden, die in der Klassendatei selbst deklariert werden.

Dateien für Kategorien erhalten konventionsgemäß einen Namen der Form klasse+kategorie.h und klasse+kategorie.m.

Wenn Sie in einer Kategorie eine Methode deklarieren, die den gleichen Namen wie eine Methode in der erweiterten Klasse hat, überschreibt die neue Deklaration die ursprüngliche Version. Der super-Aufruf hat bei Kategoriemethoden die gleiche Semantik wie bei gewöhnlichen Klassenmethoden und ruft eine eventuelle Ober- klassenversion der Methode auf und nicht die Methode aus der erweiterten Klasse. Es gibt keine Möglichkeit, eine überschriebene Methode der erweiterten Klasse aufzurufen. Es ist nicht festgelegt, was passiert, wenn mehrere Kategorien, die die gleiche Klasse erweitern, eine Methode mit dem gleichen Namen definieren. Ver- meiden Sie diese Situation.

Eine Kategorieimplementierung wird angezeigt, indem in der Klas- senimplementierung der Name der Kategorie in Klammern hinter dem Klassennamen angegeben wird:

#import "Klasse+Kategorie.h" @implementation Klasse (Kategorie) //Methodendefinitionen @end

Die Methoden einer Kategorie müssen nicht implementiert werden. Wenn ein Implementierungsabschnitt für eine Kategorie vorhanden ist, in dem die in der Kategorie deklarierten Methoden nicht voll- ständig definiert werden, meldet der Compiler eine Warnung. Gibt es keinen Implementierungsabschnitt, gibt es überhaupt keine Mel- dung. Eine deklarierte, aber nicht definierte Kategorie bezeichnet man als »informelles Protokoll« (siehe Kapitel 6, Protokolle).

Klassenerweiterungen

Klassenerweiterungen sind anonyme Kategorien. Sie werden dekla- riert, indem der Name zwischen den Klammern nach dem Klassen- namen weggelassen wird:

interface Klasse ()

Der Unterschied zu einer gewöhnlichen Kategorie ist, dass die in einer Klassenerweiterung deklarierten Methoden im eigentlichen Implementierungsabschnitt für Klasse definiert werden müssen. Klassenerweiterungen können Sie also nur für Klassen schreiben, bei denen Sie Zugriff auf den Quellcode haben.

Ab Clang/LLVM 2.0 können Klassenerweiterungen im Gegensatz zu Kategorien auch Feld- und Eigenschaftsdeklarationen enthalten.

Klassenerweiterungen werden vorwiegend eingesetzt, um die pri- vate Schnittstelle einer Klasse zu deklarieren. Dazu wird die Klassen- erweiterung in der Implementierungsdatei, nicht der Schnittstellen- datei deklariert:

#import "ding.h" @interface Ding () @end @implementation Ding @end

KAPITEL 6

Protokolle

Protokolle sind Zusammenstellungen von Methodendeklarationen, die nicht mit einer bestimmten Klasse verknüpft sind. Sie sind das Objective-C-Äquivalent zu Javas Interfaces und erfüllen die gleiche Funktion. (Genauer gesagt, entsprechen Javas Interfaces den Pro- tokollen von Objective-C, da Javas Interfaces von Objective-C-Pro- tokollen inspiriert wurden.)

Ein Protokoll wird nicht instantiiert. Klassen deklarieren ihre Kon- formität zu einem Protokoll und garantieren damit, dass ihre In- stanzen auf die entsprechenden Nachrichten reagieren. Der Zweck von Protokollen ist es, Klassen, die nicht in einem Vererbungsver- hältnis stehen, eine Übereinstimmung in Bezug auf eine spezifische Methodenschnittstelle deklarieren zu lassen. Konzeptionell sind Protokolle als weniger problematische Alternative zur Mehrfachver- erbung gedacht. Nutzen Sie Protokolle, wenn man Klassen aus den unterschiedlichsten Klassenhierarchien zu bestimmten Zwecken gleich behandeln können soll.

Ein Protokoll deklarieren

Protokolle werden mit der @protocol-Direktive deklariert:

@protocol Protokoll // Methodendeklarationen @end

Die Methodendeklarationen in einem Protokoll haben genau die gleiche Form wie bei einer Klassendeklaration, zum Beispiel:

@protocol Exportierbar -(id) exportieren; -(id) exportierenAls: (int) exportformat; -(NSArray *) exportformateAlsListe; @end

Standardmäßig sind alle Methoden erforderlich, die ein Protokoll deklarieren. Eine Klasse, die ein Protokoll adoptiert, muss alle Metho- den implementieren, die von diesem Protokoll deklariert werden.

Optionale Protokollmethoden, die implementiert werden können, aber nicht implementiert werden müssen, können mit der @optional- Direktive deklariert werden. Folgende Deklaration würde beispiels- weise die beiden letzten Methoden des Protokolls optional machen:

@protocol Exportierbar -(id) exportieren; @optional -(id) exportierenAls: (int) exportformat; -(NSArray *) exportformateAlsListe; @end