Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

Inhaltsverzeichnis
Geleitwort
Vorwort
1 Hello iPhone
2 Die Reise nach iOS
3 Sehen und anfassen
4 Alles unter Kontrolle
5 Daten, Tabellen und Controller
6 Models, Layer, Animationen
7 Programmieren, aber sicher
8 Datenserialisierung und Internetzugriff
9 Multimedia
10 Jahrmarkt der Nützlichkeiten
Stichwort

Jetzt Buch bestellen
Ihre Meinung?

Spacer
Apps programmieren für iPhone und iPad von Klaus M. Rodewig, Clemens Wagner
Das umfassende Handbuch
Buch: Apps programmieren für iPhone und iPad

Apps programmieren für iPhone und iPad
Rheinwerk Computing
1172 S., geb., mit DVD
49,90 Euro, ISBN 978-3-8362-2734-6
Pfeil 2 Die Reise nach iOS
Pfeil 2.1 Objektorientierte Programmierung
Pfeil 2.1.1 Objekte und Abstraktion
Pfeil 2.1.2 Vererbung
Pfeil 2.1.3 Überschreiben von Methoden und spätes Binden
Pfeil 2.1.4 Objektorientierung in Objective-C
Pfeil 2.1.5 Die Architektur von iOS-Programmen
Pfeil 2.2 Hefte raus, Klassenarbeit!
Pfeil 2.2.1 Controller und View in der Praxis
Pfeil 2.2.2 Modellbau
Pfeil 2.2.3 Initializer und Methoden
Pfeil 2.2.4 Vererbung
Pfeil 2.2.5 Kategorien
Pfeil 2.2.6 Protokolle
Pfeil 2.2.7 Vorwärtsdeklarationen
Pfeil 2.2.8 Kommunikation zwischen den Schichten
Pfeil 2.2.9 Delegation
Pfeil 2.2.10 Key-Value-Coding
Pfeil 2.3 Speicherverwaltung und Propertys
Pfeil 2.3.1 Stack und Heap
Pfeil 2.3.2 Starke und schwache Referenzen
Pfeil 2.3.3 Autoreleasepools
Pfeil 2.3.4 Propertys und Accessoren
Pfeil 2.4 In den Tiefen der Speicherverwaltung
Pfeil 2.4.1 Manuelles Referenzzählen
Pfeil 2.4.2 Die Speicherverwaltungsregeln für das manuelle Referenzzählen
Pfeil 2.4.3 Autoreleasepools
Pfeil 2.4.4 Der Referenzzähler
Pfeil 2.4.5 Automatisches Referenzzählen
Pfeil 2.4.6 Weakie und die starken Zeiger
Pfeil 2.4.7 Einzelgänger
Pfeil 2.4.8 Migration bestehender Projekte
Pfeil 2.5 Das Foundation-Framework
Pfeil 2.5.1 Mutables und Immutables
Pfeil 2.5.2 Elementare Klassen
Pfeil 2.5.3 Collections
Pfeil 2.6 Blöcke
Pfeil 2.6.1 Rückruffunktionen
Pfeil 2.7 Namenskonventionen
Pfeil 2.8 Zusammenfassung

2Die Reise nach iOSZur nächsten Überschrift

»Fräulein Hagebusch, wo ist denn der Scherbolzen für den Trulleberg?«
– Paul Winkelmann (Ödipussi)

Im ersten Kapitel haben Sie Ihre Arbeitsumgebung eingerichtet und eine erste App programmiert, bei der wir ganz bewusst nicht jedes Detail erklärt haben. Dieses Kapitel führt Sie in die Programmiersprache Objective-C, die objektorientierte Programmierung, die Speicherverwaltung und in weitere Themen ein, die Sie für die Erstellung anspruchsvoller iOS-Apps benötigen. Auch wenn Sie die folgenden Kapitel lesen, können Sie hierhin zurückkehren und in diesen Grundlagen nachschlagen, wenn Ihnen noch etwas unklar sein sollte.

Objective-C ist die Standard-Programmiersprache für die App-Programmierung von Apple. Es gibt zwar die Möglichkeit, plattformübergreifende Toolkits und deren Programmiersprachen zu nutzen, da das aber nicht der von Apple vorgesehene Weg ist, sind sie auch nicht Gegenstand dieses Buches. Überdies bieten plattformübergreifende Werkzeuge immer nur den kleinsten gemeinsamen Nenner für alle Plattformen, was man den resultierenden Apps häufig auch anmerkt. Für professionelle und qualitativ hochwertige Apps ist daher die native Programmierung, also die Entwicklung in der von Apple dafür vorgesehenen Programmiersprache und den dazugehörigen Bibliotheken, das Mittel der Wahl.

Objective-C ist eine Weiterentwicklung der Programmiersprache C und bringt neben einigen neuen Schlüsselwörtern in erster Linie Werkzeuge für die objektorientierte Programmierung mit. Dabei ist Objective-C sehr schlank und im Gegensatz zu seinem Vetter C++ einfacher zu lernen. Vor allem können Sie bei der Verwendung von Objective-C weitestgehend auf die gefürchtete Zeigerarithmetik von C verzichten, wodurch eine große Fehlerklasse von vornherein ausscheidet.

Eine Programmiersprache allein ist jedoch nur die halbe Miete. Um sinnvoll für eine Plattform programmieren zu können, müssen Sie sich mit den wichtigsten Bibliotheken dieser Plattform auskennen, über die Sie mit dem Betriebssystem kommunizieren, um dessen Funktionen und die Hardware zu nutzen. Die Bibliotheken sind unter iOS in die Frameworks von Cocoa Touch gekapselt. Objective-C stellt keine Mechanismen bereit, um beispielsweise auf das Adressbuch von iOS zuzugreifen – diese Mechanismen kommen aus Cocoa Touch.

Cocoa Touch enthält darüber hinaus grundlegende Konzepte, wie Speicherverwaltung, Delegation und Benachrichtigungen. All diese Dinge lernen Sie in diesem und den folgenden Kapiteln kennen. Sie werden im Zusammenhang mit Cocoa Touch häufig den Begriff Laufzeitumgebung lesen. Im Gegensatz zu Java oder .NET handelt es sich hierbei allerdings nicht um eine virtuelle Maschine mit eigener Maschinensprache. Vielmehr übersetzt der Compiler ein Objective-C-Programm in den Maschinencode des Zielrechners, also des Macs, iPhones oder iPads. Der Begriff Laufzeitumgebung beschreibt bei Cocoa Touch vielmehr Funktionalitäten, die zur Laufzeit ins Spiel kommen, wie beispielsweise die Speicherverwaltung oder Methodenaufrufe.


Rheinwerk Computing - Zum Seitenanfang

2.1Objektorientierte ProgrammierungZur nächsten ÜberschriftZur vorigen Überschrift

Objective-C ist, wie der Name vermuten lässt, eine objektorientierte Sprache, und Cocoa Touch ist ein objektorientiertes Framework. Aus diesem Grund ist zunächst ein kleiner Rundgang durch die objektorientierte Programmierung (OOP) unerlässlich. Dabei beschränken wir uns auf die für das Verständnis der iOS-Programmierung notwendigen Elemente. Wenn Sie mehr über OOP erfahren möchten, konsultieren Sie am besten ein entsprechendes Buch. Galileo Press bietet beispielsweise ein Buch über OOP als kostenloses Openbook im Web an [Anm.: Das Openbook finden Sie auch auf der DVD zum Buch.] :

http://openbook.galileocomputing.de/oop

Ein grundlegendes Konzept der OOP ist das Zusammenfassen von Daten und Operationen, während die prozedurale Programmierung Daten und Operationen (Funktionen) in der Regel trennt. Betrachten Sie beispielsweise die folgende C-Funktion computeAge. Die Funktion erhält als Eingabeparameter ein Datum und berechnet daraus das Alter:

int computeAge(long inDate) {
// lots of magic stuff here
return theAge;
}

Wie die Funktion aus der einen Zahl die andere berechnet, ist an dieser Stelle nicht relevant. Es kann über alle möglichen Anweisungen geschehen, die die Programmiersprache zulässt. Wichtig ist ein anderer Aspekt: Mit welchen Daten der Programmierer die Funktion aufruft, ist ihm freigestellt. Es gibt keinen Mechanismus, der beispielsweise einen Aufruf mit einer Längenangabe oder einer Gewichtsangabe verhindert. In diesem Fall liefert die Funktion höchstwahrscheinlich einen unsinnigen Wert zurück. Die Verantwortung für die sinnvolle Verwendung der Funktion und der Daten trägt allein der Programmierer. Er erhält dabei keine bzw. kaum Unterstützung vom Compiler, der ihn nicht vor solchen Fehlern warnen kann.

Bei kleinen Programmen, an denen ein einzelner Entwickler arbeitet und über die er den Überblick behält, ist das noch kein besonders großes Problem. Bei größeren Softwareprojekten, die neben eigenem Code auch Module und Bibliotheken anderer Programmierer einsetzen, besteht eine große Gefahr, Operationen mit unpassenden Daten zu kombinieren. Der Compiler bewahrt den Programmierer zwar davor, Operationen mit unpassend typisierten Daten auszuführen, jedoch nicht vor Operationen mit logisch unpassenden Daten.

Hier ist noch ein Beispiel für den logisch falschen, syntaktisch hingegen korrekten Umgang mit einem Datum. Ein Verkaufsprogramm von Autos enthält den selbstdefinierten Datentyp fahrzeug. Das Programm stellt ihn über eine C-Struktur dar. Er enthält verschiedene Attribute, die die Eigenschaften des Fahrzeugs repräsentieren – also beispielsweise Farbe, Alter, Preis und Art des Fahrzeugs:

struct fahrzeug {
int alter;
int preis;
char farbe[10];
...
}

Der Datentyp fahrzeug enthält nun zwar alle im Programm benötigten Eigenschaften eines Fahrzeugs, legt jedoch nicht deren korrekte Verwendung fest. Dem Programmierer steht es frei, sämtliche Eigenschaften so zu ändern, wie es der Compiler zulässt. Verändert er im Programmverlauf aus Versehen oder auch mit Absicht das Attribut alter, macht der Compiler ihn nicht darauf aufmerksam. Die Zuweisung einFahrzeug.alter = 1; ist syntaktisch korrekt. Der Compiler erkennt keinen Fehler; das Programm wird nicht abstürzen oder ein anderes Fehlverhalten aufweisen. Es wird lediglich den Verkäufer dieses Fahrzeugs erfreuen, dass sein Fahrzeug auf seltsame Art und Weise eine Verjüngungskur und damit eine implizite Wertsteigerung erfahren hat – vorausgesetzt, das Fahrzeug war vorher älter. Die zunehmende Komplexität von Software machte daher ein neues Paradigma notwendig, eine neue Art, zu programmieren, um sinnvoll und sicher mit Daten umgehen zu können. Denn insbesondere bei verteilter Programmierung verlieren selbst erfahrene Programmierer schnell den Überblick, welche Operationen mit welchen Daten möglich und erlaubt sind. Überdies ist es enorm schwierig, Code wiederzuverwenden, der aus getrennten Daten und Operationen besteht. Im vorliegenden Beispiel könnte ein Programmierer lediglich den Datentyp fahrzeug und einige Funktionen in ein eigenes Programm übernehmen, was allerdings häufig auch noch einen hohen Anpassungsaufwand erfordert.

Unter anderem diese Problematik führte zu der Entwicklung der objektorientierten Programmierung, die Daten fest mit den auf ihnen zulässigen Operationen zu Objekten verknüpft. Als erste objektorientierte Programmiersprache gilt Simula, die Ole-Johan Dahl und Kristen Nygaard Anfang der 1960er Jahre in Norwegen entwickelten. In den 1970er Jahren entwickelten Alan Kay, Dan Ingalls und Adele Goldberg die Programmiersprache Smalltalk-80 bei der Firma Xerox im Palo Alto Research Center (PARC). Sie gilt als die erste einsetzbare objektorientierte Programmiersprache, und viele Programmierer verwenden sie auch noch heutzutage. Objektorientierte Programmierung ist auf dem Zeitstrahl der IT-Geschichte also bereits lange vorhanden. Objective-C enthielt von Anfang an alle grundlegenden Konzepte der OOP, so dass im Laufe der Zeit nur sehr wenige Erweiterungen dazugekommen sind.

Die objektorientierte Programmierung basiert auf den Paradigmen Objekt, Abstraktion, Message Passing beziehungsweise Nachrichtenaustausch und Vererbung. Dabei ist ein Objekt etwas mit einem Zustand und einem Verhalten. Der Zustand beschreibt den Wert des Objekts, und das Verhalten sind die Operationen, die das Programm darauf ausführen kann.


Rheinwerk Computing - Zum Seitenanfang

2.1.1Objekte und AbstraktionZur nächsten ÜberschriftZur vorigen Überschrift

Ein Objekt kann beispielsweise eine Ampel sein, und sein Zustand (z. B. »rot«) beschreibt, welche Lampen gerade leuchten. Die Anzahl der möglichen Zustände bei diesem Objekt ist natürlich sehr begrenzt. Allerdings hat jedes Ampelobjekt einen eigenen Zustand; es sollten ja schließlich nicht alle Ampeln gleichzeitig grün sein. Das Verhalten des Ampelobjekts sind Operationen, mit denen sich sein Zustand abfragen oder ändern lässt, beispielsweise schaltet »nächste Phase« in die nächste Ampelphase, und »gib aktuelle Phase« liefert den aktuellen Zustand.

Bei der Beschreibung der Ampelobjekte haben wir nicht definiert, wie das Ampelobjekt seinen Zustand abbildet oder wie seine Operationen funktionieren. Das entspricht dem Paradigma der Abstraktion, da wir nur beschrieben haben, was das Objekt kann, aber nicht, wie es das erreicht. In der Praxis erreicht man diese Trennung durch die Datenkapselung. Dabei versteckt man alle Attribute, die den Zustand des Objekts beschreiben, vor den anderen Objekten des Programms, so dass jeweils nur das Objekt weiß, wie es seinen Zustand beschreibt. Alle anderen Objekte des Programms bekommen nur die Informationen des Objekts zu sehen, die es ihnen über Operationen bereitstellt. Zum Beispiel können Sie dadurch bei der Ampel sicherstellen, dass sie immer die Ampelphasen richtig durchläuft und kein Rowdy ihren Zustand einfach von »rot« in »grün« ändert.

Können Sie ein Geheimnis bewahren?

Dieses Vorgehen bezeichnet man auch als Geheimnisprinzip, da die Klasse verschweigt, wie sie ihre Aufgaben erledigt. Sie legt also keine Implementierungsdetails offen. Wenn Sie bei der Verwendung einer Klasse bestimmte Implementierungsdetails ausnutzen, machen Sie sich von dieser Implementierung abhängig. Wenn Sie hingegen das Geheimnisprinzip wahren und nur dokumentierte und freigegebene Operationen nutzen, kann der Entwickler der Klasse deren Implementierung austauschen, ohne dass Ihr Programm das bemerkt.

Das Geheimnisprinzip spielt gerade unter iOS eine wichtige Rolle, denn Apple lässt keine Apps für den App Store zu, die Implementierungsdetails von Cocoa Touch ausnutzen. Apple verwendet hierbei statt des Wortes Implementierungsdetail allerdings den Begriff Private API. Verwenden Sie also keine privaten APIs, denn es zahlt sich für Sie aus. Sie vereinfachen den Freigabeprozess für Ihre Apps im Store, und es hilft Ihnen dabei, stabilere Programme zu schreiben.

Die Objekte eines Programms modellieren in der Regel Dinge aus dem Anwendungsbereich des Programms. Das Adressbuch enthält beispielsweise Adressobjekte, die Adressdaten enthalten, und ein Verkehrsleitsystem operiert mit Ampeln. Das geht in der Regel sehr gut bei den Objekten, die Daten des Programms beschreiben. Ein Objekt kann jedoch auch ein Button sein, also eine Schaltfläche des Programms.

In vielen objektorientierten Sprachen beschreiben Klassen die möglichen Objekte, weswegen diese Sprachen auch klassenbasiert heißen. Objective-C ist eine klassenbasierte Sprache, bei der Sie den Objektzustand über Attribute und die Operationen über Methoden beschreiben. Für Klassen verwenden wir eine grafische Darstellung, wie sie Abbildung 2.1 zeigt, die sich an die UML, Unified Modeling Language, [Anm.: http://de.wikipedia.org/wiki/UML] anlehnt.

Abbildung

Abbildung 2.1 Eine Klasse mit Attributen und Methoden

Die Klasse Fahrzeug beschreibt nun alle Attribute und Methoden der Fahrzeugobjekte. Aufgrund der Datenkapselung dürfen jedoch nur die Methoden auf die Attribute zugreifen. Von außerhalb des Objekts ist kein direkter Zugriff auf die in den Objektattributen gespeicherten Daten möglich. Den Zugriff darauf stellt also das Objekt nur über seine Methoden bereit. In vielen objektorientierten Sprachen gibt es zwar Möglichkeiten, auf die Attribute auch von außerhalb zuzugreifen, was jedoch eine eklatante Verletzung des zentralen Paradigmas der Datenkapselung ist.

Zwischen Klassen und Objekten besteht ein Unterschied: Die Klasse ist der Bauplan eines Objekts, und ein Objekt ist eine Ausprägung einer Klasse. Das Programm erzeugt die Objekte zur Laufzeit, wobei die Klasse die Eigenschaften des Objekts definiert. Auf das Beispiel bezogen bedeutet das: Bei der Fahrzeugverwaltung haben deren Entwickler die Klasse Fahrzeug programmiert. Das Programm erstellt hingegen ein neues Fahrzeugobjekt, wenn der Nutzer ein neues Fahrzeug in den Datenbestand aufnimmt.

Abbildung

Abbildung 2.2 Die Klasse »Fahrzeug«

Für das Abrufen des Alters stellt das Objekt beispielsweise die Methode getAlter zur Verfügung. Sie liefert auf Anfrage das Alter des Fahrzeugs zurück. Ein versehentliches oder unbefugtes Überschreiben des Alters des Fahrzeugs ist nicht möglich – dafür stellt das Objekt nämlich keine Methode bereit. Der Aufruf einer Methode ähnelt dem Senden einer Nachricht an einen Empfänger, weshalb dieser Vorgang auch Message Passing beziehungsweise Nachrichtenaustausch heißt. Das Objekt, das die Nachricht erhält, heißt deswegen auch Empfänger, und entsprechend bezeichnet man die Methode als Nachricht.

Zugriffsmethoden

Eine Methode, die nur einen einzelnen Attributwert eines Objekts ausliest oder verändert, nennt man häufig auch Getter (lesender Zugriff) oder Setter (schreibender Zugriff). Getter und Setter zusammen bezeichnet man auch als Accessoren oder Zugriffsmethoden.


Rheinwerk Computing - Zum Seitenanfang

2.1.2VererbungZur nächsten ÜberschriftZur vorigen Überschrift

Ein weiteres wichtiges Merkmal objektorientierter Programmierung ist die Möglichkeit der Vererbung. Die Wiederverwendbarkeit von Code ist im Laufe der Zeit immer wichtiger geworden. »Wiederverwendbar« bedeutet dabei nicht unbedingt, dass der entsprechende Code in Bibliotheken liegt, die viele Programmierer für unterschiedliche Programme nutzen, sondern steht eher für die Vermeidung von Code-Duplikaten. Das ist gleicher oder sehr ähnlicher Programmcode, der sich an mehreren Stellen im Programm findet. Doppelter Code bläht Programme sehr stark auf und ist häufig sehr schwer zu warten, weswegen er zu den Code-Smells [Anm.: http://de.wikipedia.org/wiki/Smell_(Programmierung)] gehört.

Das Paradigma der Vererbung stellt über Variantenbildung eine sehr gute und effektive Möglichkeit bereit, die Menge des doppelten Codes sehr stark einzugrenzen. Außerdem erlaubt sie die Erweiterung von bestehendem Code, auf dessen Quellcode Sie keinen Zugriff haben, denn bei der Vererbung erstellen Sie neue Klassen auf der Basis bestehender. Dabei können Sie deren Verhalten entweder beibehalten oder überschreiben.

Um beim Beispiel der Fahrzeugverwaltung zu bleiben: Vererbung kommt dann ins Spiel, wenn sie nicht nur Fahrzeuge allgemein betrachtet, sondern auch spezialisierte Formen von Fahrzeugen wie beispielsweise Kraftfahrzeuge, Fahrräder oder Eisenbahnen. In diesem Fall bietet die Klasse Fahrzeug zwar einige grundlegende Attribute und Methoden, ist jedoch kaum dazu geeignet, die speziellen Attribute der jeweiligen Sonderformen von Fahrzeugen zu beschreiben. Andererseits sollen natürlich auch Fahrräder die Eigenschaften von Fahrzeugen haben.

Dieses Problem lässt sich durch Vererbung lösen, die eine hierarchische Verknüpfung von Klassen erlaubt. Dabei erbt eine Klasse jeweils alle Attribute und Methoden einer anderen Klasse. Die vererbende Klasse heißt dabei Superklasse oder Oberklasse, die erbende Klasse ist entsprechend die Subklasse oder Unterklasse. Das Erstellen einer Subklasse bezeichnet man auch als Ableiten. Eine abgeleitete Klasse ist in Objective-C immer auch ein Subtyp ihrer Superklasse, da Sie ihre Objekte überall da einsetzen können, wo Sie auch die Objekte der Superklasse verwenden können. Wenn Sie also Fahrrad von Fahrzeug ableiten, können Sie an allen Stellen, wo Sie Fahrzeuge verwendet haben, auch Fahrräder verwenden.

Die Subklasse erbt alle Eigenschaften und das Verhalten ihrer Superklasse und kann das Verhalten überschreiben oder erweitern. Wenn Sie von der Superklasse Fahrzeug eine Subklasse ableiten, besitzt sie zunächst einmal das gleiche Verhalten wie Fahrzeug. Sie können es jedoch erweitern oder anpassen, um das jeweilige Fahrzeug präziser beschreiben zu können.

Als Beispiel zeigt Abbildung 2.3 die von der Superklasse Fahrzeug abgeleiteten Subklassen Fahrrad und Kraftfahrzeug. Die Superklasse Fahrzeug enthält bereits einige Attribute und Methoden, die die Beschreibung eines generischen Fahrzeugs erlauben. Um weiter spezialisierte Fahrzeuge zu beschreiben, reichen diese Attribute und Methoden allerdings nicht aus. Die beiden Subklassen Fahrrad und Kraftfahrzeug bieten daher alle Attribute und Methoden der Klasse Fahrzeug und darüber hinaus weitere, notwendige Attribute und Methoden für die Beschreibung der jeweiligen Klassen. Bei einem Kraftwagen sind sicherlich die Leistung und insbesondere der Verbrauch von Interesse, die allerdings bei einem Fahrrad keine Rolle spielen und dort zudem vom Fahrer abhängen. Da ist es eher wichtig, ob das Fahrrad eine Ketten- oder eine Nabenschaltung hat.

In den obersten Superklassen innerhalb einer Klassenhierarchie finden sich daher immer alle Eigenschaften aller Klassen der Hierarchie. Diese Klassen nennt man auch Wurzelklassen. Die wichtigste Wurzelklasse in Cocoa Touch ist beispielsweise die Klasse NSObject, von der sich fast alle anderen Cocoa-Klassen ableiten.

Abbildung

Abbildung 2.3 Superklasse und Subklassen

Die Subklasse kann die von der Superklasse ererbten Methoden übernehmen oder überschreiben. Ererbte und nicht überschriebene Methoden verwenden dabei die Implementierung der Superklasse. Bei der Programmierung mit Objective-C kann eine Klasse nur von einer Superklasse erben – im Gegensatz zu C++, wo Klassen von mehreren Superklassen erben können. [Anm.: Hierfür liest man häufig den fehlerhaften Begriff Mehrfachvererbung, der jedoch insofern unsinnig ist, als eine Klasse ihr Verhalten in jeder objektorientierten Programmiersprache an beliebig viele Klassen vererben kann. Es geht hier jedoch darum, von wie vielen Klassen eine Klasse ihr Verhalten erbt.]

Sie können Vererbung natürlich über mehrere Klassenebenen einsetzen, indem Sie von den jeweiligen Subklassen wieder neue Subklassen ableiten. Somit kann eine Subklasse zugleich die Superklasse einer anderen Subklasse sein. Abbildung 2.4 zeigt ein Beispiel für eine dreistufige Vererbungshierarchie. Dabei erbt die Klasse Kraftfahrzeug ihre Verhalten von ihrer Superklasse Fahrzeug, während die Klasse LKW von der Klasse Kraftfahrzeug erbt. Ein Lastkraftwagen besitzt somit alle Eigenschaften der Klassen Fahrzeug und Kraftfahrzeug. Fahrzeug ist in diesem Fall die direkte Superklasse von Kraftfahrzeug und eine Superklasse von LKW.

Wenn ein Objekt zur Klasse LKW gehört, sagt man auch, »es ist ein LKW« und dass zwischen dem Objekt und der Klasse »eine Ist-Beziehung besteht«. Das gilt nicht nur für LKW, sondern auch für die Superklassen. Das Objekt ist also auch ein Kraftfahrzeug, und es ist ein Fahrzeug.

Abstrakte Klassen

Eine Klasse muss nicht zwingend Implementierungen für ihre Attribute und Methoden besitzen. In Klassen, die eher verallgemeinerte Konzepte, jedoch keine konkreten Bestandteile des Programms beschreiben und die nur als Generalisierung von konkreteren Subklassen dienen (beispielsweise die Klasse Fahrzeug im obenstehenden Beispiel), können Sie lediglich die für die Subklassen relevanten Attribute und Methoden anlegen, die Implementierung allerdings den Subklassen überlassen. Eine abstrakte Klasse ist also eine verallgemeinerte Beschreibung der Eigenschaften von Objekten, die erst konkrete Subklassen spezifizieren.

Objective-C unterstützt abstrakte Klassen nicht so wie viele andere objektorientierte Sprachen. Jede Klasse in Objective-C ist konkret, und Sie können somit Objekte davon erzeugen. Im Gegensatz dazu lassen sich beispielsweise in C++, C# oder Java abstrakte Klassen anlegen, von denen Sie keine Objekte erzeugen können. Sie können dort nur von ihren konkreten Subklassen Objekte anlegen.

Abbildung

Abbildung 2.4 Vererbung über drei Ebenen

Vererbung ist ein mächtiges Werkzeug für die Wiederverwendung von Code, da sie es dem Programmierer erlaubt, die Funktionalitäten vorhandener Klassen ohne großen Aufwand zu verwenden und zu erweitern. Im Gegensatz zu vielen anderen objektorientierten Sprachen bieten Objective-C und Cocoa allerdings noch weitere Möglichkeiten, Klassen zu erweitern und das Verhalten von Methoden zu beeinflussen.


Rheinwerk Computing - Zum Seitenanfang

2.1.3Überschreiben von Methoden und spätes BindenZur nächsten ÜberschriftZur vorigen Überschrift

In der objektorientierten Programmierung findet der Programmfluss durch den Austausch von Nachrichten zwischen Objekten statt. Man verwendet keine Funktionen, sondern Objekte, die über Nachrichten miteinander kommunizieren. Diese Kommunikation zwischen Objekten ist das Paradigma des Nachrichtenaustauschs. Ein Objekt sendet eine Nachricht an ein anderes Objekt.

Beim Empfangen von Nachrichten kommt ein weiteres, wichtiges Merkmal der objektorientierten Programmierung zum Tragen: das Überschreiben von Methoden. Jede Subklasse kann einerseits neue Methoden implementieren und andererseits die Methoden ihrer Superklassen überschreiben, also neu implementieren.

Alle drei Klassen in Abbildung 2.3 besitzen die Methoden mit den Namen: getAlter, getPreis, setPreis:, getFarbe, setFarbe:. Die Subklassen von Fahrzeug können diese Methoden überschreiben und die Implementierungen der Superklasse durch eigene Implementierungen ersetzen. Die Vererbung stellt hier sicher, dass das Programm immer die richtige Methode aufruft. Wenn es eine Nachricht an ein Objekt sendet, wählt das Laufzeitsystem die passende Methodenimplementierung aus. Dabei reicht für den Nachrichtenversand der Methodenname aus. Da das Programm erst zur Laufzeit die Methodenimplementierung ermittelt, also sie erst dann an den Aufruf bindet, bezeichnet man dieses Verfahren auch als spätes oder dynamisches Binden. Im Gegensatz dazu verbindet beispielsweise der Linker von Xcode bei der Programmerstellung die verwendeten C-Funktionen fest mit ihrem Aufruf. Dieses Vorgehen heißt statisches Binden, weil es bereits zur Übersetzungszeit erfolgt. Zur Laufzeit ruft das Programm für den Funktionsaufruf immer das gleiche Code-Fragment auf. Statisches Binden erfolgt also beim Programmierer und dynamisches beim Nutzer.

Wie also genau ein Objekt auf eine Nachricht reagiert, entscheidet sich bei Objective-C immer erst zur Laufzeit des Programms und nicht zum Zeitpunkt des Kompilierens. Dabei können Sie in Objective-C das dynamische Binden nicht nur innerhalb von Objekten der jeweiligen Vererbungslinien verwenden, sondern auch zwischen beliebigen Klassen, die nicht voneinander erben. Sie können in Objective-C jede Nachricht an jedes beliebige Objekt senden, da diese Programmiersprache zwischen dem Versenden und dem Empfangen von Nachrichten unterscheidet. Wenn die Klasse des Objekts die passende Methode zu der empfangenen Nachricht implementiert, ruft die Laufzeitumgebung diese Methode auf. Andernfalls behandelt es die Nachricht als Fehler. Dabei können Sie in den Klassen auch diese Fehlerbehandlung überschreiben und so auf Nachrichten reagieren, für die Ihre Klasse keine Methode bereitstellt.


Rheinwerk Computing - Zum Seitenanfang

2.1.4Objektorientierung in Objective-CZur nächsten ÜberschriftZur vorigen Überschrift

Die Programmiersprache für iOS und Cocoa Touch ist, wie bereits erwähnt, Objective-C. Sie ist eine objektorientierte Erweiterung von ANSI-C und ist auch unter OS X die Programmiersprache der Wahl. Tim Berners-Lee programmierte den ersten Webbrowser und -server unter NeXTStep in Objective-C und erstellte damit Referenzimplementierungen des von ihm erfundenen World Wide Webs.

Die 80er Jahre brachten im Zuge des Vormarschs objektorientierter Programmierung zwei objektorientierte Erweiterungen der rein prozeduralen Programmiersprache C hervor. Die eine Variante, C with classes, erlangte später unter dem Namen C++ einen großen Bekanntheitsgrad. Sie war lange Zeit die Standardsprache in der Programmierung unter Windows, bevor sich für die Anwendungsentwicklung .NET als ernstzunehmende Alternative entwickelt hat. C++ hat sich jedoch auch dank der GNU Compiler Collection (GCC) und in Form vieler proprietärer Compiler auf vielen UNIX-basierten Systemen verbreitet.

Parallel dazu kam mit Objective-C eine weitere objektorientierte Sprache auf den Markt, die auf C aufbaut. Dabei erweitert Objective-C im Gegensatz zu C++ den Sprachumfang von C, ohne ihn zu verändern, so dass Sie C-Programme auch immer mit einem Objective-C-Compiler übersetzen können. Die wichtigsten syntaktischen Erweiterungen sind dabei Klassen und Methodenaufrufe.

Von den im vorstehenden Abschnitt beschriebenen Eigenschaften objektorientierter Programmierung erfüllt Objective-C alle der vorgestellten Paradigmen und verwendet ausschließlich dynamisches Binden. Attribute heißen in Objective-C auch Objekt- oder Instanzvariablen. Der Zugriff auf die Attribute eines Objekts sollte nur über die Methoden des Objekts möglich sein. Der direkte Zugriff von außen ist zwar prinzipiell möglich, wenn Sie ihn explizit freigeben. Sie sollten das allerdings grundsätzlich vermeiden, da es ein eklatanter Verstoß gegen das Paradigma der Datenkapselung wäre.

Eine Klasse in Objective-C dient als Blaupause für Objekte. In ihr ist beschrieben, welchen Eigenschaften und Methoden die zu ihr gehörenden Objekte haben werden. Andererseits kann man eine Klasse auch selbst als Objekt auffassen – ein sogenanntes Klassenobjekt. Als solches verfügt die Klasse über Methoden. Man kann der Klasse also Nachrichten schicken, auf die sie antworten kann. Das passiert beispielsweise, wenn man ein neues Objekt dieser Klasse benötigt. Auch das Binden der Klassenmethoden erfolgt im Gegensatz zu vielen anderen objektorientierten Sprachen erst zur Laufzeit. Ein Klassenobjekt ist automatisch nach dem Start der App vorhanden; Sie müssen dafür nichts weiter tun. Das Erzeugen eines neuen Objekts zu einer bestimmten Klasse geschieht hingegen über das Versenden einer Nachricht (also durch den Aufruf einer Klassenmethode) an das betreffende Klassenobjekt, die es anweist, ein neues Objekt seiner Klasse zu erzeugen.

Variablenarten

Die Bezeichnung Instanzvariable ist nur eine von vielen Bezeichnungen für die Variablen einer Klasse. Der Begriff Instanzvariable hat sich als Bezeichnung für Attribute mittlerweile sehr stark verbreitet, obwohl er von einer unpassenden Übersetzung für das englische Wort instance herrührt. In der englischsprachigen Literatur und auch bei Apple werden Sie häufiger den Begriff ivar sehen – er bezeichnet dasselbe.

Darüber hinaus gibt es in Blöcken, Funktionen und Methoden Variablen, die nur innerhalb dieser Konstrukte gelten. Deshalb nennt man sie auch lokale Variablen. Außerdem gibt es Variablen, die als Übergabeparameter für Methoden fungieren. Sie heißen Parameter. Von der Verwendung globaler Variablen, die Sie von jeder Stelle eines Programms aus benutzen können, sollten Sie jedoch lieber absehen. Die einzige Ausnahme bilden hier globale Konstanten, die Sie jedoch nur auslesen und nicht verändern können.

In Objective-C leiten Sie eine Klassendeklaration über das Schlüsselwort @interface, den Namen der Klasse und die Angabe ihrer Superklasse ein und schließen sie mit dem Schlüsselwort @end ab. Die Klasse trennen Sie dabei durch einen Doppelpunkt von ihrer Superklasse, und darauf kann ein Block für die Attributdeklarationen folgen, so dass eine Deklaration für die Klasse Fahrzeug beispielsweise so aussehen kann:

@interface Fahrzeug : NSObject {
// Attributdeklarationen
}
// Property- und Methodendeklarationen
@end

Listing 2.1 Eine Klassendeklaration in Objective-C

Hinter dem Attributblock – beziehungsweise direkt auf die Superklasse bei Klassendeklarationen ohne Attributblock – folgen die Deklarationen der Methoden und der Propertys der Klasse. Dabei sind Propertys eine gute Möglichkeit, auf Attribute zu verzichten, da Attributdeklarationen eine Verletzung des Geheimnisprinzips darstellen, denn sie legen ja das Implementierungsdetail offen, wie Sie den Zustand der Objekte in Ihrer Klasse darstellen. [Anm.: Wir behandeln den Attributblock im Rahmen dieses Buches auch nur, weil Sie noch häufig Klassen antreffen, die ihn verwenden.]

Die Implementierung einer Klasse leiten Sie mit dem Schlüsselwort @implementation, gefolgt vom Klassennamen, ein, worauf ein optionaler Attributblock folgen kann, und Sie beenden sie mit dem Schlüsselwort @end. Zwischen der Einleitung und @end stehen dann die Methodendefinitionen, also die Implementierungen der Methoden der Klasse.

@implementation Fahrzeug {
// Attributdeklarationen
}
// Methodenimplementierungen
@end

Listing 2.2 Implementierung einer Klasse

Hier stellt der Attributblock natürlich keine Verletzung des Geheimnisprinzips dar, weil er sich ja in der Implementierungsdatei der Klasse befindet, die nicht für andere Teile des Programms sichtbar ist. Sie bekommen ja von Apple auch nur die Header-, aber nicht die Implementierungsdateien zu sehen.

Objective-C verwendet im Umgang mit den von C übernommenen Datentypen dieselbe Typisierung wie C – statisch, schwach und explizit. Sie ist statisch, da der Typ einer Variablen schon zur Übersetzungszeit feststeht. Implizite und explizite Typumwandlung sind möglich, können jedoch sowohl in C als auch in Objective-C mitunter zu Fehlern führen. Aus diesem Grund bezeichnet man die Typsysteme der beiden Sprachen als schwach, da sie zur Laufzeit Typen nicht immer richtig konvertieren.

Darüber hinaus bietet Objective-C für Objekte das Konzept der dynamischen Typisierung. Hierfür existiert der Typ id, der einen Zeiger auf ein beliebiges Objekt repräsentiert. Er ist dabei unabhängig davon, zu welcher Klasse das Objekt gehört. Sie können jedes Objekt einer Variablen vom Typ id zuweisen und umgekehrt jeder Variablen einer bestimmten Klasse einen id-Wert. Bei Letzterem müssen Sie natürlich sicherstellen, dass das zugewiesene Objekt und die Klasse der Variablen zueinander passen. Dazu ein kleines Beispiel:

id theString = @"12345"; // Okay, NSString -> id
NSString *theText = theString; // Okay, NSString -> NSString
NSNumber *theValue = theString; // logischer Fehler
char theCharacter = [theValue charValue]; // Laufzeitfehler

Listing 2.3 Typzuweisungen mit »id«

Alle vier Anweisungen in Listing 2.3 führen nicht zu einem Übersetzungsfehler, jedoch sind nur die beiden ersten unproblematisch. Die erste Anweisung legt eine Variable vom Typ id mit dem Namen theString an und weist ihr eine Zeichenkette zu, was vollkommen legitim ist. Auch die zweite Anweisung, die eine weitere Variable des Typs NSString * mit dem Namen theText anlegt und ihr den Wert der ersten Variablen zuweist, ist unproblematisch.

Konstante Zeichenketten

In Objective-C erzeugen Sie über die Notation @"..." eine konstante Zeichenkette. Sie hat die Klasse NSString, ist ein Objekt und verhält sich auch so. Sie können also auch Nachrichten an sie schicken. Beispielsweise liefert [@"2" length] die Länge der Zeichenkette mit dem Inhalt »2«, also den Wert 1. Das @-Zeichen ist dabei übrigens wichtig. Wenn Sie es weglassen, erzeugen Sie kein Objekt, sondern einen C-String. Glücklicherweise warnt Xcode Sie jedoch schon vor der Übersetzung des Codes vor vergessenen »Klammeraffen«.

Variablen auf Objekte

In Objective-C sind Variablen auf Objekte immer Zeiger, was Sie an dem Stern hinter dem Klassennamen erkennen. Ihn wegzulassen ist ein Fehler, und Xcode beziehungsweise der Compiler schimpfen in diesem Fall mit Ihnen. Eine Variable verweist also genau genommen auf ein Objekt und enthält es nicht.

Die dritte Zuweisung führt weder zu einem Übersetzungs- noch zu einem Laufzeitfehler, obwohl hier eine Variable des Typs NSNumber * eine Zeichenkette zugewiesen bekommt. Objective-C wandelt Zeichenketten nicht automatisch in Zahlen um, auch wenn die Zeichenkette nur aus Ziffern besteht. Erst wenn das Programm (wie in der letzten Zeile) eine unbekannte Nachricht an das Objekt sendet, kommt es zu einem Laufzeitfehler, da die Klasse NSString die Nachricht charValue nicht versteht.

Sie können an id-Werte jede beliebige Nachricht senden. Einen Laufzeitfehler wie in Listing 2.3 können Sie also auch einfach durch [theString charValue] erzeugen.

Typlosigkeit nur bedingt

Der Typ id lässt keine direkten Rückschlüsse darauf zu, welchen Typ dieses Objekt besitzt. Sie können die Klasse eines Objekts jedoch zur Laufzeit über die Methode class abfragen. Diese dynamische Typisierung erleichtert die Programmierung, da Sie sich keine Gedanken darüber machen müssen, wie genau ein Objekt aussieht. Sie können durch Verwendung des Typs id erheblich an Flexibilität gewinnen. Allerdings verzichten Sie damit auf die Erkennung von Typfehlern zur Übersetzungszeit, wie Listing 2.3 zeigt. Sie sollten also gerade am Anfang Ihrer Programmierer-Karriere typisierte Variablen bevorzugen, um Typfehler schon zur Übersetzungszeit erkennen zu können.

Da Sie Klassen in Objective-C immer vollständig implementieren müssen, können Sie immer auch Objekte aller Klassen erzeugen. Sie haben also keine Möglichkeit, abstrakte Klassen zu erstellen, von denen sich keine direkten Objekte erzeugen lassen. Sie können nur über Konventionen (z. B. die Dokumentation, Regeln für die Klassennamen) festlegen, welche Klassen abstrakt sind. Abstrakte Klassen in anderen Programmiersprachen deklarieren häufig Methoden, ohne diese zu implementieren. Sie sollten diese abstrakten Methoden so implementieren, dass sie eine Warnung ausgeben oder noch besser einen Programmfehler verursachen, damit Sie möglichst früh bemerken, dass Sie die entsprechende Methode noch implementieren müssen.

Die Syntax für den Nachrichtenversand von Objective-C ist für viele Umsteiger aus anderen Programmiersprachen ungewohnt, da sich die Schreibweise dafür nicht wie in C++, Java und C# an Funktionsaufrufe anlehnt. Das Vorbild ist vielmehr die Syntax von Smalltalk. In Listing 2.4 sehen Sie ein kurzes Objective-C-Programm mit einigen Methodenaufrufen.

Fahrzeug *fahrzeug = [[Fahrzeug alloc] init];
[fahrzeug setPreis:500];

Listing 2.4 Methodenaufrufe in Objective-C

Die erste Anweisung versendet bereits zwei Nachrichten. Die Klasse Fahrzeug bekommt zunächst die Nachricht alloc geschickt und das Ergebnis dieses Aufrufes die Nachricht init. Die Anweisung Fahrzeug fahrzeug = [[Fahrzeug alloc] init] besteht also aus zwei Teilen. Die erste Anweisung, [Fahrzeug alloc], erzeugt ein neues, nicht initialisiertes Objekt der Klasse Fahrzeug, das danach die Nachricht init erhält.

Methodenaufrufe in Java und C++

Zum Vergleich: Der Code aus Listing 2.4 entspricht von den Methodenaufrufen her dem Code

Fahrzeug fahrzeug = Fahrzeug.alloc().init();
fahrzeug.setPreis(500);

in Java, wenn Sie ihn wörtlich übersetzen. Allerdings verwendet Java zur Erzeugung neuer Objekte den Operator new, so dass

Fahrzeug fahrzeug = new Fahrzeug();
fahrzeug.setPreis(500);

einer besseren Übersetzung des Codes entspricht, und eine Übersetzung nach C++ sieht der Java-Version sehr ähnlich:

Fahrzeug *fahrzeug = new Fahrzeug();
fahrzeug->setPreis(500);

Nachrichtenketten dieser Art treten sehr häufig in Objective-C auf, da sie den Speicherplatz für Objekte belegen und initialisieren. Aus anderen Programmiersprachen kennen Sie vielleicht Konstruktoren, die diese zwei Schritte übernehmen.

Für das Versenden einer Nachricht verwendet Objective-C die Notation mit den eckigen Klammern, die der Sprache Smalltalk entlehnt ist. Im Gegensatz zu vielen anderen Programmiersprachen besteht ein Methodenname in der Regel nicht aus einem Bezeichner und einer Parameterliste, sondern aus mehreren Bezeichnern mit den Parametern dazwischen:

- (double)berechneKosten:(double)inPreis 
zuStrecke:(double)inStrecke {
...
}
// Nachrichtenversand
double kosten = [fahrzeug berechneKosten:1.59
zuStrecke:235.0];

Listing 2.5 Methodenimplementierung und -aufruf in Objective-C

Objective-C hat also in der Regel keine Parameterliste, sondern benennt die Parameter explizit. Dieses Vorgehen hat den Vorteil, dass Sie auch beim Nachrichtenversand, wie Sie in der letzten Zeile in Listing 2.5 sehen, was die einzelnen Parameter bedeuten. Gerade bei Parametern des gleichen Typs hilft das anderen Programmierern, Ihren Code zu verstehen. Das erste Element innerhalb der eckigen Klammern ist immer der Nachrichtenempfänger, den Sie im Gegensatz zu C++ und Java niemals weglassen dürfen – auch nicht, wenn ein Objekt eine Nachricht an sich selbst schickt; in diesem Fall bezeichnen Sie den Empfänger mit dem Schlüsselwort self:

[self berechneKosten:1.59 zuStrecke:235.0]

Objective-C verwendet einen Selektor, um eine Nachricht zu versenden. Eine Nachricht besteht also aus einem Selektor und den Parameterwerten. Das Laufzeitsystem sucht damit die passende Methodenimplementierung in der Objektklasse und führt sie im Erfolgsfall aus. Selektoren beschreiben also passende Methoden für den Nachrichtenempfang. Da Objective-C Methoden nicht anhand der Parametertypen unterscheiden kann, besteht ein Selektor nur aus dem reinen Methodennamen.

Damit der Compiler weiß, dass es sich bei einer Zeichenfolge um einen Selektor handelt, müssen Sie sie mit dem Schlüsselwort @selector kennzeichnen. Der Selektor zu der Methode aus Listing 2.5 lautet also @selector(berechneKosten:zuStrecke:). Dabei sind die Doppelpunkte wichtig, da sie die Positionen der Parameter im Selektor angeben, und beispielsweise bezeichnet @selector(setPreis) eine parameterlose Methode, im Gegensatz zu @selector(setPreis:), die eine Methode mit einem Parameter bezeichnet.

Stilfrage

Objective-C tendiert zu sehr langen Methodennamen. In diesem Buch werden Sie Methodennamen begegnen, die nicht in eine Zeile passen. Thats not a bugits a feature. Diese Methodennamen beschreiben in der Regel auch die Parameter, da dies zu lesbarerem und verständlicherem Code führt. Der Leser eines Programmcodes hat ja nicht immer die Deklarationen zu jeder Nachricht parat, und da vereinfachen solche Methodennamen das Verständnis sehr. Ein Programm zu schreiben ist nicht schwer. Dagegen ist es eine Kunst, den Programmcode so zu verfassen, dass andere ihn verstehen.

Apple verwendet für Methodennamen den Lower Camel Case, also zusammengesetzte Wörter mit Großbuchstaben am Anfang der inneren Wörter, jedoch mit einem Kleinbuchstaben am Anfang (»hörtSichSchwierigerAnAlsEsIst«). Im Gegensatz dazu schreibt man Klassen im Upper Camel Case, bei dem der erste Buchstabe ein Großbuchstabe ist (»AuchNichtSchwerZuVerstehen«).

Die Methode berechneKosten:zuStrecke: hat jedoch noch einen gekürzten Namen, um Sie nicht direkt mit einem zu langen Wort zu schocken. Sie beschreibt ja nicht ihren ersten Parameter – sie sollte also besser berechneKostenMitPreis:zuStrecke: heißen.

Die Programme der restlichen Kapitel dieses Buches verwenden ausschließlich englische Bezeichner. Das hat zwei Gründe: Zum einen lassen sich viele Begriffe nur sehr schlecht ins Deutsche übertragen. Zum anderen ist es beim Lesen anstrengender, zwischen zwei Sprachen zu wechseln, da ja alle Bezeichner von Cocoa Touch englisch sind.

Welchen Stil Sie für Ihre Programme verwenden, bleibt natürlich Ihnen überlassen. Seien Sie jedoch nicht zu geizig mit der Länge Ihrer Methodennamen – Xcode hat ja glücklicherweise eine automatische Vervollständigungsfunktion im Editor. Die sprechenden Methodennamen haben den ungeheuren Vorteil, dass sie den Code wesentlich lesbarer machen, da der Name im Prinzip ja schon die Dokumentation der Methode enthält.

Ein weiteres wichtiges Merkmal objektorientierter, dynamischer Programmierung ist die Fähigkeit der Objekte, Kenntnis über sich selbst zu haben. Diese Fähigkeit heißt deshalb Reflexion oder auch Introspektion. In Objective-C können Sie beispielsweise eine Klasse oder ein Objekt fragen, ob sie bzw. es bestimmte Nachrichten versteht.

Alle Objekte, auch die Klassenobjekte, besitzen die Methode respondsToSelector:, die jeweils alle Wurzelklassen in Cocoa Touch implementiert. Sie lässt sich also auf eine Klasse oder ein Objekt anwenden und gibt YES zurück, wenn das Objekt die angefragte Nachricht versteht. Andernfalls ist der Rückgabewert NO. Hier sind ein paar Beispiele für diese Abfragen:

Fahrzeug *fahrzeug = ...;
Fahrrad *fahrrad = ...;
if([fahrzeug respondsToSelector:@selector(getFarbe)]) {
...
}
if([fahrrad respondsToSelector:@selector(getFarbe)]) {
...
}
if([fahrrad respondsToSelector:@selector(getLeistung)]) {
...
}
if([fahrrad respondsToSelector:@selector(getFarbe:)]) {
...
}

Listing 2.6 Abfrage auf Nachrichten

Die erste Bedingung ist wahr, weil die Klasse Fahrzeug die Methode bereitstellt. Da die Klasse Fahrrad eine Subklasse von Fahrzeug ist, ist auch die zweite Bedingung wahr. Hingegen ist die vorletzte Bedingung falsch, weil weder Fahrrad noch irgendeine ihrer Superklassen die Methode getLeistung implementiert. Auch die letzte Abfrage ist falsch, da die Methode getFarbe keinen Parameter erwartet. Der Selektor enthält hingegen einen Doppelpunkt am Ende, der für einen Parameter steht. Die Selektoren @selector(getFarbe) und @selector(getFarbe:) beschreiben ja unterschiedliche Methodennamen.

Sie können die Nachricht respondsToSelector: auch an Klassen schicken, um die Existenz von Klassenmethoden zu überprüfen. Beispielsweise ist der Aufruf [Fahrrad respondsToSelector:@selector(alloc)] wahr, da die Klasse Fahrrad diese Klassenmethode von NSObject erbt. [Fahrrad respondsToSelector:@selector(init)] ist hingegen falsch, da init keine Klassenmethode ist. Sie können dennoch eine Klasse über die Methode instancesRespondToSelector: nach der Existenz von Objektmethoden befragen, und so ist der Ausdruck [Fahrrad instancesRespondToSelector:@selector(init)] wahr.

Abfragen der Eigenschaften

Im Programmcode ist es häufig günstiger, das Objekt statt die Klasse nach ihren Eigenschaften zu fragen, da Sie ja die Klassen und damit ihre Methoden kennen. Von einem Objekt kennen Sie jedoch zur Übersetzungszeit nicht unbedingt die Klasse, die es zur Laufzeit hat. Da ist es dann natürlich wesentlich sinnvoller, die Existenz von bestimmten Methoden abzufragen.

Außerdem können Sie ja das Klassenobjekt eines Objektes über die Methode class abfragen. Die Fähigkeit der Reflexion ist ein extrem mächtiges Werkzeug, mit dem sich beispielsweise die dynamische Erzeugung von Objekten, dynamische Programmmodule und Plugins sowie Objektserialisierung umsetzen lassen. In Cocoa Touch gibt es sogar dafür bereits fertige Klassen. Sie sollten die Reflexion jedoch nicht für Fallunterscheidungen zur Laufzeit verwenden. Das können Sie in Objective-C eleganter über Kategorien lösen, die Sie später noch kennenlernen (siehe Abschnitt 2.2.5).


Rheinwerk Computing - Zum Seitenanfang

2.1.5Die Architektur von iOS-ProgrammenZur vorigen Überschrift

Für das Beispielprogramm im ersten Kapitel hat Xcode diverse Dateien angelegt. Wozu dienen die alle? Wenn der View – also die Ansicht – der App im Storyboard liegt, wozu dient dann die Controller-Klasse? Die Antwort auf diese Fragen liefert das Architekturmuster Model-View-Controller (MVC), das Apple für alle iOS-Applikationen empfiehlt und das fundamental für die iOS-Programmierung ist.

Das Model-View-Controller-Muster wurde Ende der 1970er Jahre zusammen mit der Programmiersprache Smalltalk entwickelt. Es beschreibt eine Struktur zum Aufbau von Applikationen und zählt deswegen zu den Architekturmustern und nicht zu den Entwurfsmustern, die nur Lösungen für Teilprobleme einer Applikation beschreiben. Die Struktur soll dabei die Wartbarkeit und die Erweiterbarkeit des Programmcodes verbessern. Sie unterteilt die Applikationen in sinnvolle Komponenten und erleichtert dadurch die Entwicklung größerer Anwendungen.

MVC teilt die Klassen eines Programms in drei Bereiche auf, die man auch Ebenen oder Schichten nennt. Das Modell verwaltet die Daten des Programms. Diese Schicht kapselt also die Daten der Anwendung in Klassen. In dem Programm Adressbuch sind beispielsweise diese Daten die gespeicherten Adressen. Das Modell des Adressbuches besitzt also Klassen zur Speicherung und Verarbeitung von Adressen. Die Aufgabe des Modells ist es auch, die Daten konsistent zu halten. Das bedeutet, dass ein gutes Datenmodell die Anwendungsdaten fehlerfrei abbilden sollte. Im Adressbuch können Sie beispielsweise keinen vollständig leeren Eintrag speichern. Für das Datenmodell des Adressbuchs ist ein leerer Eintrag ein Fehler, den es nicht zulässt.

Die Viewschicht von MVC enthält alle Klassen, die für die direkte Interaktion des Programms mit dem Nutzer zuständig sind. Das sind sowohl Klassen, die etwas anzeigen, als auch Klassen, die Benutzereingaben verarbeiten können, und viele Viewklassen können auch beides. Typische Beispiele für Views sind Bilder, Buttons, Textfelder und Fenster. Neben diesen Standardelementen, die iOS zur Verfügung stellt, können Sie auch applikationsspezifische Views entwickeln, die spezielle Darstellungs- und Eingabemöglichkeiten haben.

Der Controller im MVC-Muster stellt schließlich das Bindeglied zwischen dem Modell und den Views dar. Er übernimmt die Steuerung des Programms und enthält die Applikationslogik. Der Controller darf auf die Komponenten des Modells und des Views zugreifen und auch deren Methoden direkt aufrufen. Xcode legt bei viewbasierten Projekten immer automatisch mindestens einen Viewcontroller an, der die Steuerung eines Dialogs übernimmt.

Abbildung

Abbildung 2.5 Das MVC-Architekturmuster in der Übersicht

Umgekehrt sollten allerdings weder das Modell noch der View die Klassen oder Methoden des Controllers kennen. Sie bedienen sich verschiedener Techniken, um mit dem Controller zu kommunizieren. Der View benachrichtigt den Controller über den Target-Action-Mechanismus, den Sie im ersten Kapitel bereits verwendet haben, als Sie einen Button mit einer Methode verbunden haben. Eine weitere Möglichkeit ist die Delegation. Die Kommunikation vom Modell an den Controller erfolgt über Notifications und Key-Value-Observing. Auf diese Techniken gehen wir natürlich im Einzelnen noch genauer ein.

Machen Sie sich nicht abhängig!

Die Modell- und die Viewschicht sollten jeweils keine Abhängigkeit von den beiden anderen Schichten aufweisen. Abhängigkeit bezeichnet dabei die Verwendung der Klassen der anderen Ebenen. Verwenden Sie also keine Controller- oder Viewklassen bei der Implementierung des Modells und auch keine Controller- oder Modellklassen bei der Implementierung Ihrer Views.

Sie erhöhen so die Wiederverwendbarkeit dieser beiden Schichten. Das bedeutet beim Modell, dass Sie jedes seiner Objekte durch unterschiedliche Controller verarbeiten und über verschiedene Views darstellen können. Andererseits sollten Sie Ihre Viewklassen so entwickeln, dass sie sich auch für ähnliche Anwendungsfälle eignen. Denken Sie an die Viewklassen von Cocoa Touch (wie Buttons, Slider oder Textfelder), die Sie in vollkommen unterschiedlichen Applikationen einsetzen können.

Die stringente Anwendung von MVC erleichtert überdies die Portabilität von Apps. Für die Portierung von iPhone-Apps zum iPad müssen Sie in der Regel die Views-Schicht sehr stark anpassen, während der Controller geringere Anpassungen benötigt. Das Modell sollte in den meisten Fällen gleich bleiben. Ein sauber implementiertes Modell können Sie unter Umständen sogar in OS X-Applikationen verwenden.



Ihre Meinung

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.

<< zurück




Copyright © Rheinwerk Verlag GmbH, Bonn 2014
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: 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.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern


  Zum Rheinwerk-Shop
Zum Katalog: Apps programmieren für iPhone und iPad






Neuauflage: Apps programmieren für iPhone und iPad
Jetzt Buch bestellen


 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Einstieg in Objective-C 2.0 und Cocoa






Einstieg in Objective-C 2.0 und Cocoa


Zum Katalog: Spieleprogrammierung mit Android Studio






Spieleprogrammierung mit Android Studio


Zum Katalog: Android 5






Android 5


Zum Katalog: iPhone und iPad-Apps entwickeln






iPhone und iPad-Apps entwickeln


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
InfoInfo