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

Rheinwerk Computing - Zum Seitenanfang

2.2Hefte raus, Klassenarbeit!Zur nächsten Überschrift

Jetzt ist es an der Zeit, die Grundlagen aus dem vorangegangenen Abschnitt durch ein größeres Beispielprojekt zu illustrieren. Das hier vorgestellte Beispiel finden Sie auf der Buch-DVD sowie im Git-Repository zum Buch.

Versionsverwaltung

Sie werden eine Versionsverwaltung auch für kleine Projekte schätzen lernen, sobald Sie sich auch nur einmal im Dschungel Ihrer verschiedenen, über unzählige Ordner und Rechner verstreuten Versionen des Sourcecodes Ihres Projekts verirrt haben und aus dem wirren Wust keine übersetzungsfähige Version mehr erzeugen können. Für die Zusammenarbeit mit anderen Entwicklern ist eine Versionsverwaltung schlicht unverzichtbar.

Die Mutter aller Versionsverwaltungen ist CVS, das Concurrent Versions System, das lange Zeit der Standard gerade im Open-Source-Bereich war. Aufgrund zahlreicher konzeptioneller Eigenheiten ist CVS jedoch nicht mehr zeitgemäß und wurde weitgehend von den Systemen Subversion (SVN) und Git abgelöst.

Ursprünglich hat der Linux-Erfinder Linus Torvalds Git entwickelt, und es hat sich im Open-Source-Bereich gerade für große Projekte als Standard durchgesetzt. Der Linux-Kernel, Android, Gnome, VLC und viele andere bekannte (und noch mehr unbekannte) Projekte verwenden Git. Es hat sich bei der Verwaltung des Linux-Kernels bewährt und auf diese Weise seine Eignung auch für große Projekte unter Beweis gestellt.

Xcode unterstützt Git erst ab Version 4, hat es seitdem allerdings auch zur Standardversionsverwaltung erkoren.

Sie sollten möglichst jedes neue Xcode-Projekt als Git-Repository einrichten, sofern Sie nicht ein anderes Repository verwenden wollen. Einen umfassenden Einblick in die Arbeit mit Git bietet Kapitel 10, »Jahrmarkt der Nützlichkeiten«. Die Sourcen aller Beispiele im Buch finden Sie in dem öffentlich zugänglichen Git-Repository Github unter der URL https://github.com/Cocoaneheads/iPhone.

Legen Sie für das folgende Beispiel ein neues Projekt in Xcode an. Öffnen Sie dazu den Projekt-Wizard, und erstellen Sie ein iOS-Projekt vom Typ Single View Application (siehe Abbildung 2.6).

Abbildung

Abbildung 2.6 Das Template für das neue Projekt

Nach dem Drücken des Buttons Next zeigt Ihnen Xcode den in Abbildung 2.7 gezeigten Dialog an, in dem Sie den Namen des Projekts und Ihrer Firma (Organisation) sowie Ihre Firmenkennung festlegen, wie Sie das bereits in Kapitel 1, »Hello iPhone«, gemacht haben.

Projektinformation

Den Quellcode des folgenden Beispiels finden Sie auf der DVD unter Code/Apps/iOS7/Kapitel2 oder im Github-Repository zum Buch im Unterverzeichnis https://github.com/Cocoaneheads/iPhone/tree/Auflage_3/Apps/iOS7/Kapitel2.

Abbildung

Abbildung 2.7 Die Einstellungen beim Anlegen des Projekts


Rheinwerk Computing - Zum Seitenanfang

2.2.1Controller und View in der PraxisZur nächsten ÜberschriftZur vorigen Überschrift

Xcode legt automatisch einen Viewcontroller an, womit das erste MVC-Element schon im Projekt vorhanden ist – der Controller. Der Controller ist das zentrale Element der App; er stellt die Verbindung zwischen dem View und dem Modell her.

Nach Beenden des Projekt-Wizards öffnen Sie die Datei Main.storyboard. Die Hauptansicht von Xcode zeigt in der Mitte eine grafische Darstellung des Viewcontrollers mit seinem View an. Diesen Editor von Xcode bezeichnet man auch als Interface-Builder.

Der View ist noch eine leere, weiße Fläche. Ziehen Sie aus der Objektbibliothek rechts unten in der IDE einen Text-View darauf (siehe Abbildung 2.8). Dabei darf er ruhig die komplette Fläche belegen. Sie erweitern dadurch die Viewschicht des Projekts, um darüber die Interaktion mit dem Benutzer zu ermöglichen.

Öffnen Sie anschließend die Dateien Main.storyboard und ViewController.h im Editor und Hilfseditor von Xcode, analog zum Beispiel in Kapitel 1 oder über die Tastenkombination alt+cmd+¢. Danach ziehen Sie bei gedrückter ctrl-Taste eine Verbindung vom Text-View in die Klassendeklaration in der Interface-Datei (siehe Abbildung 2.9).

Abbildung

Abbildung 2.8 Hinzufügen eines Text-Views zum GUI

Abbildung

Abbildung 2.9 So verbinden Sie den Text-View mit dem Controller.

Geben Sie dem Outlet den Namen Text-View (siehe Abbildung 2.10), und weisen Sie Xcode durch einen Klick auf den Connect-Button an, die Verknüpfung und den entsprechenden Code zu erstellen.

Abbildung

Abbildung 2.10 Die Eigenschaften des neuen Outlets festlegen

Wie das Label bei der Applikation aus Kapitel 1 können Sie den Text-View nun über das Outlet textView ansprechen, da Sie eine Verbindung vom Controller zum View erstellt haben. Dies machen Sie sich gleich zunutze, um Textmeldungen direkt auf dem GUI der App auszugeben.

Wie Sie bereits wissen, verteilt sich der Quelltext einer Klasse in Objective-C auf zwei Dateien: auf die Headerdatei mit der Dateiendung .h und auf die Implementierungsdatei mit der Endung .m. Die Headerdatei enthält die Schnittstelle (das Interface) der Klasse nach außen, während die Implementierungsdatei die Umsetzung enthält, auf die andere Klassen keinen Zugriff haben.

Die Implementierungsdatei enthält den ausführbaren Code einer Klasse. Die Implementierung einer Klasse ist geheim. Sie geht nur den Programmierer etwas an. Damit andere Programmierer diese Klasse benutzen können, reicht es, die Headerdatei und die kompilierte Klasse weiterzugeben. Die Headerdatei deklariert, welche Methoden eine Klasse besitzt. Auf diese Weise ist es möglich, kompilierte Klassen zur freien Verwendung weiterzugeben, ohne den Code offenlegen zu müssen.

Attribute in der Klassendeklaration

Bei älteren Objective-C-Compilern musste die Klassendeklaration auch alle Attribute der Klasse enthalten. Da Attribute jedoch zu den Implementierungsdetails einer Klasse zählen, ist das eine Verletzung der Datenkapselung und somit des Geheimnisprinzips. Alle Compiler, die Apple seit Xcode 4 ausgeliefert hat, unterstützen Möglichkeiten, auf diese Attributdeklarationen zu verzichten.

Apple gibt Ihnen beispielsweise nicht den Quelltext seiner Frameworks. Was Sie auf der Festplatte Ihres Rechners finden, sind Headerdateien. Diese brauchen Sie für die Programmierung Ihrer eigenen Apps. Wie Apple die Klassen implementiert hat, bleibt Ihnen jedoch verborgen, denn die Implementierungsdateien liegen gut verwahrt auf den Servern in Cupertino. Das ist auch gut so, denn Sie sollten sich beim Programmieren nur auf die Teile des Codes verlassen, die Apple auch dokumentiert hat.

Kommen wir zurück zum Beispiel. Ändern Sie im Sinne einer besseren Übersichtlichkeit die Schriftgröße des eben zum GUI hinzugefügten Text-Views, indem Sie den Text-View anklicken und im Attributinspektor die Größe des Fonts auf 12.0 stellen (siehe Mauszeiger in Abbildung 2.11).

Abbildung

Abbildung 2.11 Ändern der Schriftgröße des Text-Views

Der Viewcontroller, der im Code durch die Headerdatei ViewController.h und die Implementierungsdatei ViewController.m repräsentiert wird, besitzt verschiedene Methoden, die im Zusammenhang mit seinem Lebenszyklus stehen. Bevor Sie eine dieser Methoden benutzen, implementieren Sie zunächst eine eigene, neue Methode, die dazu dient, Textausgaben in den Text-View zu schreiben. Öffnen Sie dazu die Datei ViewController.m, und fügen Sie vor dem Schlüsselwort @end die folgende Methode ein:

-(void)writeLog:(NSString *)inLogString{
NSLog(@"[+] %@.%@", self,
NSStringFromSelector(_cmd));
NSDateFormatter *theFormatter =
[[NSDateFormatter alloc] init];

[theFormatter setDateFormat:@"HH:mm:ss.SSS"];
[self.textView setText:[NSString
stringWithFormat:@"%@\n%@ [+] %@",
[self.textView text],
[theFormatter stringFromDate:[NSDate date]],
inLogString]];
}

Listing 2.7 Ausgabe einer Meldung in den Text-View

Die Methode dient dazu, Meldungen mit einem Zeitstempel in den Text-View zu schreiben. Anschließend erweitern Sie die Methode viewDidLoad um zwei Anweisungen, so dass die Methode wie folgt aussieht:

- (void)viewDidLoad {
[super viewDidLoad];
[self.textView setText:@""];
[self writeLog:@"viewDidLoad"];
}

Listing 2.8 Ausgabe der ersten Meldung

Wenn Sie die App anschließend mit cmd+R oder über den Run-Button links oben in der Xcode-Werkzeugleiste übersetzen und starten, sollten Sie im Simulator die Ausgabe aus Abbildung 2.12 sehen.

Was haben Sie jetzt eigentlich programmiert? Gehen wir den neuen Code einfach zeilenweise durch:

-(void)writeLog:(NSString *)inLogString {

Hiermit haben Sie eine Methode mit dem Namen writeLog: angelegt, die als Rückgabetyp void hat, also nichts zurückgibt. Die Methode akzeptiert einen Parameter vom Typ NSString – also eine Zeichenkette – mit dem Namen inLogString.

NSLog(@"[+] %@.%@", self, NSStringFromSelector(_cmd));

Abbildung

Abbildung 2.12 Log-Ausgabe im »UITextView«

Der Funktionsaufruf von NSLog in der zweiten Zeile dient zur Ausgabe von Text in die Systemkonsole. Der erste Parameter für NSLog entspricht den von C bekannten Formatstrings, also einer Zeichenkette mit Platzhaltern, gefolgt von den Daten, die an die Stelle der Platzhalter kommen sollen. Wie Sie ja bereits wissen, macht der Klammeraffe vor dem ersten Anführungszeichen aus der Zeichenkette ein NSString-Objekt.

Der Platzhalter %@ steht für ein Objekt und ist somit eine Erweiterung der Format-Platzhalter von C, wo es ja keine Objekte gibt. Die Daten für die beiden %@-Platzhalter liefern die Ausdrücke self und NSStringFromSelector(_cmd). Die Variable self steht Ihnen in jeder Methode zur Verfügung und verweist auf das aktuelle Objekt. Das gilt übrigens auch für Klassenmethoden, wo self auf das entsprechende Klassenobjekt verweist. Die Variable _cmd steht Ihnen ebenfalls in jeder Methode zur Verfügung und enthält jeweils deren Selektor. Diese Funktion NSStringFromSelector wandelt nun einen Selektor in eine Zeichenkette um. Der Funktionsaufruf liefert also immer den Namen der aktuellen Methode; in diesem Fall ist das writeLog:.

Super Nachricht!

Listing 2.8 verwendet neben self die Variable super, die ebenfalls das aktuelle Objekt bezeichnet. Im Gegensatz zu self ignoriert super jedoch die Methoden der aktuellen Klassenimplementierung und verwendet nur die Methoden der Superklassen; in diesem Fall ist das nur die Implementierung der Methode viewDidLoad Klasse UIViewController. Das ist an dieser Stelle auch notwendig, da eine Endlosrekursion entstünde, wenn self der Empfänger der Nachricht viewDidLoad wäre. Die Methode würde sich dadurch immer wieder selbst aufrufen.

Sie sehen diese Ausgabe, wenn Sie (wie in Abbildung 2.13 gezeigt) die Konsolenansicht in Xcode öffnen und anschließend die App erneut ausführen. Sie erhalten in der Konsolenansicht folgende Ausgabe:

2012-10-07 12:51:04.383 Kapitel2[8056:c07] [+] 
<ViewController: 0x715f240>.writeLog:

Dabei hat die Methode NSLog die Variable self in die Zeichenkette <ViewController 0x715f240> umgewandelt, die ihren Klassennamen und die Speicherstelle des Objekts im Hauptspeicher angibt.

Abbildung

Abbildung 2.13 Öffnen der Konsolenansicht

Diese Art des Loggings ist beim Verfolgen des Programmablaufs extrem hilfreich, insbesondere dann, wenn Sie Fehler suchen. Die Konsole bezeichnet man nicht umsonst auch als »Debugger für Arme«.

Die nächste Anweisung der Methode writeLog:

NSDateFormatter *theFormatter = [[NSDateFormatter alloc] init];

erzeugt Sie ein neues Objekt der Klasse NSDateFormatter und weist es der Variablen theFormatter zu. Dazu ruft sie die Klassenmethode alloc dieser Klasse auf und sendet der dadurch neu erzeugten Instanz anschließend die Nachricht init. Über diese zwei Aufrufe erzeugen Sie ja in vielen Fällen neue Objekte.

NSDateFormatter ist eine Klasse, mit der Sie Zeitstempel formatieren; Sie wandeln damit also einen Zeitstempel in eine Zeichenkette mit einem vorgegebenen Aufbau um. Jede Log-Ausgabe auf dem Text-View soll eine Zeitangabe enthalten, die die Stunde, Minute, Sekunde und Millisekunde umfasst. Dazu legen Sie in der nächsten Zeile über den Methodenaufruf setDateFormat: das gewünschte Format fest. Den Aufbau dieser Zeichenkette beschreiben Sie ebenfalls über eine Zeichenkette:

[theFormatter setDateFormat:@"HH:mm:ss.SSS"];

Dabei stehen HH, mm und ss jeweils für die zweistellige Ausgabe von Stunden, Minuten und Sekunden, und die Angabe SSS wandelt die Millisekunden in eine dreistellige Zeichenkette um.

Danach schreibt die Methode die Nachricht in den Text-View. Dabei soll sie jedoch nicht den bestehenden Inhalt löschen, sondern nur den neuen anhängen. Das erreicht sie, indem sie den alten Inhalt ausliest, ihn über die Methode stringWithFormat: mit dem neuen Wert verknüpft und ihn über die Methode setText: wieder an den Text-View übergibt. Dabei funktioniert die Klassenmethode stringWithFormat: ähnlich wie die Funktion NSLog: Sie übergeben im ersten Parameter ein Format mit Platzhaltern, deren Werte Sie in den folgenden Parametern festlegen. Die Escape-Sequenz \n sorgt übrigens für einen Zeilenvorschub zwischen dem alten Inhalt des Text-Views und der neuen Meldung.

[self.textView setText:[NSString stringWithFormat: 
@"%@\n%@ [+] %@", [self.textView text],
[theFormatter stringFromDate:[NSDate date]], logString]];

Für den ersten Wert verwenden Sie den oben erzeugten Formatter mit der Methode stringFromDate:, die einen Zeitstempel in eine Zeichenkette mit dem vorgegebenen Aufbau umwandelt. Den dafür notwendigen Zeitstempel, der die aktuelle Zeit enthält, erzeugen Sie über die Klassenmethode date der Klasse NSDate. Da diese Methode ein neues, bereits initialisiertes Objekt erzeugt, zählt man sie zu den Convenience-Konstruktoren. Das sind Klassenmethoden, die die Objekterzeugung gegenüber alloc und init etwas vereinfachen.

Damit ist die Methode zum Schreiben von Log-Ausgaben in den Text-View komplett. Jetzt betrachten Sie noch die zweite Änderung im Viewcontroller, die Sie vorgenommen haben – die Methode viewDidLoad. Die erste Anweisung ruft die Implementierung dieser Methode in der Superklasse auf.

- (void)viewDidLoad {
[super viewDidLoad];

Damit der User diesen Beispieltext (oder einen anderen Text, der zufällig im Text-View steht) beim Laden der App nicht angezeigt bekommt, löscht die nächste Anweisung den Inhalt des Text-Views, indem sie dem Text-View die Nachricht setText: mit einer leeren Zeichenkette sendet:

    [self.textView setText:@""];

In der nächsten Zeile ruft die Methode writeLog: auf und übergibt ihr als Parameter den Namen der aktuellen Methode als Zeichenkette. Der Empfänger self in diesem Aufruf bezeichnet das aufrufende Objekt selbst; es schickt also eine Nachricht zum Aufruf von writeLog: an sich. Selbstgespräche von Objekten sind in objektorientierten Programmiersprachen nicht unüblich.

    [self writeLog:[NSString stringWithFormat:@"%@", 
NSStringFromSelector(_cmd)]];

Rheinwerk Computing - Zum Seitenanfang

2.2.2ModellbauZur nächsten ÜberschriftZur vorigen Überschrift

Controller und View sind vorhanden, nun fehlt noch die Modellschicht, um MVC komplett zu machen. Viele Apps kommen auch ohne Modell aus, da sie beispielsweise nur extrem geringe Datenmengen verwalten. Bei solchen Anwendungen übernimmt dann häufig der Controller auch die Aufgaben des Modells. Ein Beispiel dafür ist die Wecker-App aus dem dritten Kapitel. Sie besitzt kein eigenständiges Modell, weil ihre Daten nur aus der aktuellen Zeit und der Weckzeit bestehen. Eigene Modellklassen dafür würden diese App unnötig verkomplizieren.

Das Beispielprojekt in diesem Kapitel soll jedoch ein Modell mit einer eigenen Klasse erhalten, die Sie nun zum Projekt hinzufügen. Wie wir bereits erwähnt haben, ist in Objective-C eine Klasse die Konstruktionszeichnung für ein Objekt. Klassen können keine Daten speichern oder Berechnungen anstellen, Sie müssen dafür erst Objekte erzeugen. Dies erfolgt, wie Sie bereits gesehen haben, über einen Methodenaufruf an die Klasse des Objekts; also eine Klassenmethode, mit der Sie das Klassenobjekt anweisen, ein Objekt zu erzeugen. Die dazu aufgerufene Methode trägt den Namen alloc. Alle Klassen, die von NSObject abstammen, kennen diese Methode, da die Klasse NSObject sie als Klassenmethode implementiert.

Die Klassenmethode alloc

In der Datei NSObject.h sehen Sie, dass alloc eine Klassenmethode ist:

+ (id)alloc;

Das vorangestellte Pluszeichen weist alloc als Klassenmethode aus. Instanzmethoden, also Methoden von Objekten, beginnen mit einem Minuszeichen, z. B.:

- (id)init;

alloc erzeugt ein Objekt der jeweiligen Klasse. Das bedeutet, dass alloc den Speicher für das Objekt reserviert, alle Attribute auf 0 beziehungsweise nil setzt und einen Zeiger auf das Objekt zurückliefert.

Ein mit alloc erzeugtes Objekt ist also sozusagen leer. Um das Objekt direkt nach der Erzeugung zur Verwendung vorzubereiten, es also direkt mit sinnvollen Daten zu füllen, sollte unmittelbar nach dem Aufruf der Klassenmethode alloc die Methode init oder eine andere Initialisierungsmethode aufgerufen werden. Eine Initialisierungsmethode ähnelt den aus anderen Programmiersprachen bekannten Konstruktoren. Alle Subklassen von NSObject erben oder überschreiben die Methode init.

Neben init sind auch andere Initialisierungsmethoden mit Parameterübergabe möglich. Dabei ist es eine Konvention – die Sie auch unbedingt einhalten sollten –, dass Initialisierungsmethoden mit dem Präfix init beginnen; typische Namen sind beispielsweise initWithString:, initWithFrame: oder initWithNibNamed:bundle:. Im Gegensatz zu anderen Methoden sollten und dürfen Sie die Initialisierungsmethoden nur genau einmal nach der Erzeugung des Objekts aufrufen, wobei Ihnen diese Namenskonvention hilft, diese Methoden besser zu erkennen.

Kehren Sie dazu zu dem oben angelegten Projekt zurück, und fügen Sie eine neue Klasse hinzu. Dies können Sie entweder über den Menüpunkt FileNewFile... tun oder mit einem Mausklick über den Utilities-Bereich von Xcode. Der Utilities-Bereich verfügt im rechten unteren Bereich des Xcode-Fensters über eine File Template Library, eine Sammlung von Schablonen für neue Dateien (siehe Abbildung 2.14), die Sie über den Button 1 aktivieren.

Abbildung

Abbildung 2.14 Eine Schablone für eine neue Klasse auswählen ...

Sie können übrigens über das Icon 2 links neben dem Suchfeld auf die Listenansicht umschalten. Dann zeigt Xcode zu den Vorlagen auch eine kurze Beschreibung an. Wenn Sie einen Eintrag anklicken, sehen Sie eine ausführlichere Beschreibung 3 in einem Pop-over-Fenster.

Suchen Sie in dieser Library nach der Vorlage für eine Objective-C class für Cocoa Touch, und ziehen Sie diese links in die Dateiübersicht von Xcode. Am besten fügen Sie die neuen Dateien in die gleiche Gruppe (das sind die gelben Ordner in der Dateiübersicht) wie die anderen Klassen ein (siehe Abbildung 2.15).

Abbildung

Abbildung 2.15 ... und per Drag & Drop zum Projekt hinzufügen

Nach dem Ziehen in die Dateiübersicht öffnet sich ein Dialog, in dem Sie den Namen der neu hinzugefügten Klasse vergeben. Auch wenn als Dateiendung in diesem Dialog nur .m zu sehen ist, erzeugt Xcode automatisch eine Header- und eine Implementierungsdatei – Sie müssen sich nicht darum kümmern. Geben Sie der Klasse den Namen »Model«, und schließen Sie den Dialog (siehe Abbildung 2.16). Damit haben Sie die Klasse zum Xcode-Projekt hinzugefügt.

Abbildung

Abbildung 2.16 Ein Name für die neue Klasse

Die Klasse soll nicht nur durch ihre Anwesenheit glänzen, sondern Eigenschaften und Methoden besitzen. Traditionell deklarieren Sie Attribute in Objective-C in der Klassendeklaration:

@interface Model : NSObject {
@private
NSString *status;
NSDate *creation;
NSString *name;
NSMutableArray *objects;
}
@end

Listing 2.9 Klassendeklaration mit Attributen

Das Schlüsselwort @private vor diesem Attribut kennzeichnet, dass nur die eigene Klasse die Attribute lesen darf.

Halt, noch nicht abschreiben!

Wie bereits erwähnt, sind Attributdeklarationen in der Schnittstelle eine Verletzung des Geheimnisprinzips, da sie Implementierungsdetails ausplaudern. Da es jedoch früher nicht möglich war, ohne dieses Konstrukt auszukommen, finden Sie auch noch heute sehr viele Klassendeklarationen mit Attributen. Beispielsweise gehören auch sehr viele Klassen aus Cocoa Touch dazu. Sie erfahren gleich, wie Sie auf diese Attributdeklarationen verzichten können, und Listing 2.9 soll lediglich veranschaulichen, wie solche veralteten Deklarationen aussehen.

Da der Zugriff auf diese Attribute im Sinne der Datenkapselung nicht direkt, sondern über entsprechende Methoden – die Accessoren – erfolgt, müssen Sie die entsprechenden Methoden schreiben. Die Methode zum Lesen eines Attributs bezeichnet man dabei als Getter, die Methode zum Setzen als Setter. Das Programmieren von Gettern und Settern ist reine Fleißarbeit. Für das Lesen und Schreiben des Attributs name sind beispielsweise die folgenden Zugriffsmethoden notwendig:

- (NSString *)name {
return name;
}

- (void)setName:(NSString *)inName {
name = inName;
}

Listing 2.10 Zugriffsmethoden in Objective-C

Seit Objective-C 2.0 gibt es mit den Propertys eine bessere Alternative zu Attributen und selbstgeschriebenen Accessoren. Bei der Verwendung von Propertys kann der Compiler selbständig Accessoren anlegen, wodurch für Sie lästige Tipparbeit entfällt. Das Interface der Klasse Model ändert sich durch die Verwendung von Propertys wie folgt:

@interface Model : NSObject
@property(copy) NSString *status;
@property(strong) NSDate *creation;
@property(copy) NSString *name;
@end

Listing 2.11 Klassendeklaration mit Propertys

Das Attribut objects haben wir ersatzlos aus der Klassendeklaration gestrichen, weil es keine öffentliche Property abbilden soll und wir seine Deklaration deshalb in die Implementierungsdatei verschoben haben, wie Sie Listing 2.12 entnehmen können.

@implementation Model {
@private
NSMutableArray *objects;
}
@end

Listing 2.12 Attributdeklaration am Implementierungsblock

Die drei restlichen Attribute lassen sich problemlos als Propertys deklarieren. Dies macht das Schlüsselwort @property deutlich, auf das die in Klammern gesetzten Optionen, der Typ und der Name der Property folgen. Mehr als das müssen Sie nicht tun, um Propertys zu einer Klasse hinzuzufügen.

@synthesize

Wenn Sie Xcode in einer Version bis 4.4 verwenden, müssen Sie den Compiler – neben der Deklaration über @property im Interface – auch über die Direktive @synthesize in der Implementierungsdatei anweisen, die Property zu implementieren. Zu der Deklaration

@property (copy) NSString *name;

in der Headerdatei mussten Sie also in der Implementierungsdatei ein entsprechendes

@synthesize name;

einfügen. Wenn Sie den Compiler LLVM 4.1 oder neuer nutzen, den Apple ab Xcode 4.5 ausliefert, ist das glücklicherweise nicht mehr notwendig. Jetzt reicht die Property-Deklaration vollkommen aus. Andererseits können Sie auch die @synthesize-Anweisung noch getrost in den Implementierungsblock schreiben, damit Ihr Code auch mit älteren Compilerversionen kompatibel bleibt. Für die Klasse Model sehen diese Anweisungen so aus:

@synthesize status;
@synthesize name;
@synthesize creation;

Mit den @synthesize-Anweisungen heißen die Instanzvariablen genau so wie die Propertys, also status, name und creation. Ohne diese Anweisungen heißen die Variablen wie die Propertys, jedoch mit einem Unterstrich davor, also _status, _name und _creation.

Alternativ können Sie noch den Namen des Attributs in der @synthesize-Anweisung mit angeben. Dazu schreiben Sie den gewünschten Namen einfach durch ein Gleichheitszeichen getrennt hinter den Property-Namen, wobei Sie den Attributnamen unabhängig vom Property-Namen wählen können, z. B.:

@synthesize status = meinStatus;

Mit dieser Anweisung können Sie auf die Instanzvariable dann über den Namen meinStatus zugreifen, während Sie für die Accessoren nach wie vor self.status verwenden müssen.

In der Implementierungsdatei Model.m legen Sie die @synthesize-Anweisungen für die drei Propertys an. Damit sieht der Implementierungsblock folgendermaßen aus:

@implementation Model {
@private
NSMutableArray *objects;
}

@synthesize status;
@synthesize name;
@synthesize creation;

@end

Listing 2.13 Synthetisierung der Propertys

Der Compiler erzeugt jetzt für jedes Attribut eine Getter-Methode mit dem Namen der Property und eine Setter-Methode, die den Namen mit vorangestelltem Präfix set besitzt, also für die Property status die Methoden status und setStatus. Außerdem erzeugt er ein Attribut (siehe letzter Kasten), das Sie in Ihren Methoden verwenden können.

Die synthetischen Getter und Setter legt der Compiler jedoch nur an, wenn Sie sie nicht selbst anlegen. Sie können also trotz @synthesize auch eigene Getter und Setter implementieren. Wenn Sie beide Zugriffsmethoden selbst implementieren, müssen Sie @synthesize verwenden, damit Ihnen der Compiler das Attribut anlegt.

Die Punktnotation

Objective-C erlaubt für den Aufruf von Zugriffsmethoden eine alternative Syntax. Anstelle der eckigen Klammern verwenden Sie hierfür einen Punkt, und bei den Settern lassen Sie das Präfix set weg. Die Anweisung

[[self user] setName:[textField text]];

können Sie mit der Punktnotation auch kurz als

self.user.name = textField.text;

schreiben. Es ist allerdings auch hier ein Unterschied, ob Sie self.user oder einfach nur user schreiben. Während [self user] und self.user Methodenaufrufe sind, ist user nur ein direkter Attributzugriff ohne Methodenaufruf. Zwischen der Notation mit eckigen Klammern und der Punktnotation besteht also nur ein syntaktischer, jedoch kein semantischer Unterschied, wie er zwischen self.user und user besteht.


Rheinwerk Computing - Zum Seitenanfang

2.2.3Initializer und MethodenZur nächsten ÜberschriftZur vorigen Überschrift

Die Klasse Model verfügt nun über Eigenschaften mit den entsprechenden Zugriffsmethoden. Es wäre jetzt schön, wenn ein Objekt direkt nach seiner Erzeugung, also in der Initialisierung, sinnvolle Werte für seine Propertys erhielte. Das Objekt erst zu initialisieren und danach von außen die Property-Werte zu setzen, ist häufig umständlich oder auch nicht sinnvoll. Sie können allerdings die Methode init der Klasse NSObject überschreiben, um ein Objekt mit den gewünschten Werten vorzubelegen.

Xcode bringt praktischerweise nicht nur Datei-Templates mit, sondern auch Code-Schnipsel (auch Snippets genannt), die Sie direkt in Ihren Code ziehen können. Unter diesen Snippets befindet sich die Vorlage für eine init-Methode. Wählen Sie in der Utilities-Spalte rechts im Xcode-Fenster unten die Code Snippet Library aus (siehe Abbildung 2.17), und geben Sie in das Suchfeld ganz unten im Fenster »init« ein. Xcode zeigt Ihnen alle verfügbaren Snippets an, die auf diesen Suchbegriff passen.

Abbildung

Abbildung 2.17 Auswahl des Snippets für eine »init«-Methode

Wählen Sie das Snippet Objective-C init Method aus, und ziehen Sie es in die Implementierungsdatei der Klasse Model in der Datei Model.m zwischen die @implementation- und die @end-Anweisung (siehe Abbildung 2.18). Alternativ können Sie an dieser Stelle auch einfach ein »i« tippen, das Xcode dann in grauer Schrift zum Wort »init« ergänzt. Die IDE schlägt Ihnen dadurch vor, Ihre Eingabe durch den Schnipsel für init zu ersetzen, was Sie durch Drücken der ¢-Taste bestätigen können.

Abbildung

Abbildung 2.18 Ziehen Sie die Methode direkt in die Implementierung.

Die Implementierung in Listing 2.14 sollte nun so wie in Abbildung 2.18 aussehen. Durch Drücken der Tab-Taste bewegen Sie den Cursor an die Stelle <#initializations#>, die der Editor auch hervorhebt.

#import "Model.h"

@implementation Model {
@private
NSMutableArray *objects;
}

@synthesize status;
@synthesize name;
@synthesize creation;

- (instancetype)init {
self = [super init];
if (self != nil) {
<#initializations#>
}
return self;
}
@end

Listing 2.14 Implementierung der Klasse »Model«

Die Importanweisung

Die Präprozessordirektive #import ist eine Objective-C-Erweiterung, die wie das aus C bekannte #include eine Headerdatei einbindet. Im Gegensatz zu #include vermeidet #import jedoch das versehentliche mehrfache Einbinden von Headerdateien durch verschachtelte #import-Anweisungen. Sie brauchen also nicht zu überprüfen, ob Sie eine Datei bereits importiert haben, wie das bei #include notwendig ist.

Bei <#initializations#> erweitern Sie die neue Methode um zwei Anweisungen. Die erste weist dem Attribut creation das aktuelle Datum zu, und die andere initialisiert das Attribut objects mit einem Array-Objekt. Nach dieser Änderung sollte die Methode also wie folgt aussehen:

- (id)init {
self = [super init];
if (self != nil) {
self.creation = [NSDate date];
objects = [[NSMutableArray alloc] init];
}
return self;
}

Listing 2.15 Initialisierung von Property- und Attributwerten

Die Property creation enthält nun also automatisch den Zeitpunkt, an dem das Programm das Objekt erzeugt hat, und durch die Zuweisung eines Objekts der Klasse NSMutableArray können Sie über das Attribut objects weitere Objekte speichern.

Die Klasse Model besitzt überdies die Property name, die einen eindeutigen Bezeichner aufnehmen soll, falls die App verschiedene Modellobjekte erzeugt. Das Zuweisen des Namens kann natürlich auch von außen über den Aufruf des entsprechenden Setters erfolgen. Allerdings müssen Sie dazu nach dem Erzeugen und Initialisieren des Objekts eine weitere Nachricht zum Einrichten des Objekts senden. Da sich dieser Schritt jedoch auch zur Objektinitialisierung zählen lässt, können Sie einen weiteren Initializer mit einem Parameter implementieren. Diese neue Methode deklarieren Sie zunächst in der Headerdatei:

- (instancetype)initWithName:(NSString*)inName;

Listing 2.16 Deklaration eines Initializers mit Parametern

Sie besitzt einen Parameter für den Namen, und ihre Implementierung sieht wie folgt aus:

- (id)initWithName:(NSString*)inName {
self = [self init];
if(self != nil) {
self.name = inName;
}
return self;
}

Listing 2.17 Initializer mit Parameterübergabe

Sie ruft zuerst die init-Methode der Klasse Model auf, um auch die Property creation und das Attribut objects zu initialisieren. Anstatt die Variable self über den Aufruf der init-Methode der Superklasse zu initialisieren, dürfen Sie also auch einen anderen Initializer aus der Klasse Model verwenden, wenn dabei kein Zyklus entsteht. Das würde beispielsweise passieren, wenn init die Variable self über self = [self initWithName:@"Modell"]; initialisierte. Dann würden sich die Methoden gegenseitig aufrufen, und es entstünde eine Endlosschleife, die das Betriebssystem erst abbricht, wenn der Speicher voll ist.

Designierter Initializer

Die Klasse Model verfügt jetzt über mehrere Initializer, von denen nur einer die eigentliche Initialisierungsaufgabe übernimmt. Diesen Initializer nennt man den designierten Initializer. Der designierte Initializer muss auch immer die Initialisierung der Superklasse ausführen, da es andernfalls eine Initialisierungsmöglichkeit gäbe, die das nicht macht. Das würde zu unvollständig initialisierten Objekten führen. Jeder Initializer muss also – direkt oder indirekt – einen Initializer der Superklasse aufrufen.

Mit den Methoden init beziehungsweise initWithName: können Sie nun neue Objekte der Klasse im Programm erzeugen. Der Viewcontroller ist die koordinierende Instanz der App, daher ist er auch eine prädestinierte Stelle für die Erzeugung und Verwaltung eines Modells. Fügen Sie in der Headerdatei ViewController.h eine Importanweisung für die Klasse Model hinzu: #import "Model.h". Dadurch machen Sie das Modell dem Controller bekannt, und Sie können die Klasse Model und ihre Methoden in der Header- und Implementierungsdatei des Controllers verwenden, ohne Compiler-Warnungen und Fehler zu provozieren.

Fügen Sie nun eine neue Property mit dem Namen model zu der Klasse ViewController hinzu. Diese Property soll auf das zu erzeugende Objekt der Klasse Model verweisen:

@property (strong) Model *model;

Damit ist die Deklaration der Property model abgeschlossen. Sie können sie nun verwenden, müssen sie allerdings vor der erstmaligen Verwendung noch initialisieren. Damit das Objekt vor der ersten Anzeige des Viewcontrollers zur Verfügung steht, fügen Sie in der Datei ViewController.m in der Methode viewDidLoad den folgenden Aufruf hinzu:

self.model = [[Model alloc] initWithName:@"LoremIpsum"];

Listing 2.18 Initialisierung der Property für das Modell

Listing 2.18 verwendet also Ihren soeben erstellten Initializer initWithName:, an den Sie die Zeichenkette @"LoremIpsum" als Namen des Modells übergeben. Ansonsten erfolgt die Erzeugung des Objekts wie bisher: Zuerst wird alloc und danach die Initialisierungsmethode aufgerufen.

Abbildung 2.19 zeigt ein Sequenzdiagramm des Kommunikationsablaufs. Im ersten Schritt 1 sendet der Viewcontroller die Nachricht alloc an die Klasse Model. Sie erzeugt ein neues, uninitialisiertes Objekt dieser Klasse und liefert es zurück 2. In Schritt 3 sendet der Viewcontroller die Nachricht initWithName: mit dem Parameter @"LoremIpsum" an dieses frisch erzeugte Objekt. Diese Methode liefert den Empfänger der initWithName:-Nachricht, also das neu erzeugte Modellobjekt, zurück 4.

Abbildung

Abbildung 2.19 Ablauf der Objekterzeugung

Um dieses Konstrukt zu überprüfen, fragen Sie nach der Erzeugung des Modells dessen Property name in der Methode viewDidLoad ab und übergeben das Ergebnis an die Methode writeLog:, um es im View der App anzuzeigen:

[self writeLog:[NSString stringWithFormat:@"Model.name: %@", 
[self.model name]]];

Lassen Sie das Programm übersetzen und ausführen. Dann sollte die Anzeige aus Abbildung 2.20 erscheinen.

Abbildung

Abbildung 2.20 Die Ausgabe nach der Initialisierung

Voilà, die Initialisierung über den erweiterten Initializer mit Parameter funktioniert. Das Modell soll – als Stellvertreter einer wie auch immer gearteten Programmlogik in »echten« Apps – im weiteren Verlauf des Kapitels Objekte der Klasse Droid verwalten. Sie fügen zunächst also eine weitere Klasse zum Projekt hinzu. Dies erfolgt wie bereits bei der Klasse Model beschrieben, indem Sie die entsprechende Vorlage aus der Bibliothek in die Dateiansicht Ihres Projekts ziehen. Geben Sie der neuen Klasse den Namen Droid.

Die Klasse erhält eine Property und eine Methode initWithID:

@property(copy) NSString *droidID;

- (id)initWithID:(NSInteger)inID;

Fügen Sie diese Deklarationen in die Headerdatei Droid.h der Klasse ein, und öffnen Sie anschließend die Implementierungsdatei Droid.m.

Zahltypen in Objective-C

In Objective-C können Sie Zahlen über elementare Datentypen, wie int, float, NSInteger, NSUInteger, oder über die Klasse NSNumber darstellen. Ein elementarer Datentyp hat keine Methoden, und Sie weisen seinen Variablen einfach einen Wert zu, beispielsweise:

int theRadius = 3;
NSInteger theDiameter = 2 * theRadius;
float theCircumference = theDiameter * 3.14159;

Im Gegensatz dazu erzeugen Sie Objekte von NSNumber wie andere Objekte auch. Da Sie ein NSNumber-Objekt jedoch nach seiner Erzeugung nicht mehr ändern können, müssen Sie seinen Wert schon bei der Initialisierung festlegen:

NSNumber *theRadius = [[NSNumber alloc] initWithInt:3];
NSNumber *theDiameter = [[NSNumber alloc] initWithInteger:
2 * [theRadius intValue]];
NSNumber *theCircumference = [[NSNumber alloc]
initWithInteger:[theDiameter integerValue] * 3.14159];

Wie Sie an dem Beispiel sehen, kann ein NSNumber-Objekt sowohl ganze Zahlen als auch Fließkommawerte aufnehmen.

Dort sorgen Sie durch die @synthesize-Anweisung für die automatische Erzeugung der Property und implementieren die Methode initWithID: wie folgt:

@implementation Droid

@synthesize droidID;

- (id)initWithID:(int)inID {
self = [super init];
if(self != nil){
self.droidID = [NSString stringWithFormat:
@"0xDEADBEEF%i", inID];
}
return self;
}

Listing 2.19 Initialisierung eines Droiden

Die Methode initWithID: in Listing 2.19 ruft die Initialisierung der Superklasse auf und weist der Property droidID anschließend die Zeichenkette 0xDEADBEEF zu, auf die eine Zahl folgt, die der Methode übergeben wurde.

Das Array objects des Modells soll Objekte der Klasse Droid aufnehmen. Dazu importieren Sie zunächst die Headerdatei Droid.h in der Implementierungsdatei Model.m, indem Sie am Anfang hinter der Anweisung #import "Model.h" die Anweisung #import "Droid.h" einfügen. Die Methode updateDroids: in Listing 2.20 legt entweder einen neuen Droiden im Modell an oder zerstört den letzten.

- (void)updateDroids:(int)inValue {
if(inValue > [objects count]){
Droid *theDroid;

theDroid = [[Droid alloc] initWithID:inValue];
[objects addObject:theDroid];
}
else if(inValue < [objects count]) {
[objects removeLastObject];
}
}

Listing 2.20 Verwaltung der Droiden

Die Methode vergleicht die übergebene Zahl mit der Anzahl der Objekte im Array objects, die Sie über die Methode count ermitteln können. Dabei dient das Array dazu, mehrere Objekte zu speichern. Die Bedingung inValue > [objects count] ist also wahr, wenn der Wert in inValue größer ist als die Anzahl der Objekte in objects. In diesem Fall legt die Methode einen neuen Droiden an und fügt ihn über die Methode addObject: der Klasse NSMutableArray in das Array objects ein; das ist die letzte Zeile des ersten if-Blocks. Der zweite if-Block löscht das letzte Objekt in dem Array über die Methode removeLastObject, wenn der Parameterwert kleiner als die Anzahl der Objekte im Array ist.

Die neue Methode soll später auch der Viewcontroller – also eine andere Klasse – aufrufen können. Dazu müssen Sie sie in der Headerdatei deklarieren, da sie zurzeit nur in der Implementierungsdatei bekannt ist. Fügen Sie die Methodendeklaration in die der Klasse ein, wie es Listing 2.21 hervorgehoben darstellt:

@interface Model : NSObject

@property(copy) NSString *;
@property(strong) NSDate *creation;
@property(copy) NSString *name;

-
(void)updateDroids:(int)inValue;

@end

Listing 2.21 Methodendeklaration in der Klasse »Model«

Um die neue Methode auszuprobieren, erweitern Sie den View in der Datei Main.storyboard Ihres Projekts so, wie es Abbildung 2.21 zeigt. Dazu selektieren Sie zunächst den Text-View, so dass Sie die Markierungen für die Größenänderung sehen können, und schieben die untere Kante ein Stück nach oben. Als Nächstes ziehen Sie aus der Objektbibliothek jeweils einen Button, einen Stepper und ein Label in den Bereich unter den Text-View, und geben Sie dem Button noch einen sinnvolleren Namen. Da der Nutzer sich über diesen Button die Einträge des Modells ansehen kann, ist »List« eine passende Bezeichnung. Buttons und Labels kennen Sie bereits aus dem ersten Kapitel. Ein Stepper ist das mittlere Element in der Abbildung; es hat die Klasse UIStepper und erlaubt die schrittweise Erhöhung oder Verringerung eines Wertes.

Abbildung

Abbildung 2.21 Erweiterung des Views um drei neue Elemente

Sie können den Wertebereich und die Schrittweite des Steppers über dessen Attributinspektor einstellen, den Sie über die Tastenkombination alt+cmd+4 öffnen, wenn Sie ihn zuvor selektiert haben. Den Wertebereich legen Sie über die Felder Minimum und Maximum, die Schrittweite über Step und den aktuellen Wert über Current fest. Verwenden Sie als Einstellung dafür jeweils die in Abbildung 2.22 gezeigten Werte. Damit erhöht der Stepper seinen Wert um 1, wenn Sie seinen Plus-Button drücken, und verringert ihn entsprechend durch Drücken des Minus-Buttons. Dabei bleibt der Wert allerdings immer zwischen 0 und 100.

Abbildung

Abbildung 2.22 Wertebereich und Schrittweite des Steppers einstellen

Als Nächstes öffnen Sie den Hilfseditor über alt+cmd+¢ und ziehen eine Verbindung vom Stepper in die Klassendeklaration des Viewcontrollers. In dem Pop-up wählen Sie unter Connection den Eintrag Action aus. Geben Sie der Action den Namen updateCountOfDroids. Die anderen Einstellungen lassen Sie unverändert und drücken Connect. Xcode legt danach eine Deklaration und eine Definition für diese neue Methode an. Methoden, die wie updateCountOfDroids: mit einem Eingabeelement verbunden sind beziehungsweise werden können, bezeichnet man auch als Action-Methoden.

Die Methodendefinition befindet sich natürlich in der Implementierungsdatei ViewController.m des Viewcontrollers. Die neue Methode soll nun die Anzahl der Droiden im Modell ändern, indem sie den Wert des Steppers als Parameter beim Aufruf der Methode updateDroids: des Modells verwendet. Um an den Wert des Steppers zu kommen, könnten Sie ein Outlet anlegen, wie Sie es bereits für den Text-View gemacht haben. Allerdings übergibt Ihnen Cocoa Touch praktischerweise diesen View auch als Parameterwert für die Methode updateCountOfDroids:, so dass Sie den Wert über [sender value] ermitteln können.

Jedoch warnt Xcode Sie bei Verwendung dieses Ausdrucks, da der Parameter sender den Typ id hat und es mehrere Klassen gibt, die eine Methode value deklarieren. Leider haben die Deklarationen unterschiedliche Rückgabetypen, so dass hier ein mehrdeutiger Ausdruck vorliegt. Sie können diesen Missstand jedoch leicht beheben, indem Sie in der Definition den Typ des Parameters in UIStepper * ändern:

- (IBAction)updateCountOfDroids:(UIStepper *)sender {
int theValue = [sender value];

[self.model updateDroids:theValue];
}

Listing 2.22 Übergabe des Stepper-Wertes an das Model

Sprechende Methodennamen

Der Methodenname updateCountOfDroids: drückt schon relativ gut aus, was diese Methode macht, und er ist daher wesentlich besser als die gern verwendeten nichtssagenden Namen click: oder buttonClick:, die man in vielen Programmen sieht. Ein guter Methodenname sollte immer beschreiben, was die Methode macht. Sprechende Bezeichner erleichtern das Verständnis des Codes erheblich.

Da die Methode jetzt einen Stepper als Parameter erwartet, können Sie das gegebenenfalls auch im Namen ausdrücken und sie in updateCountOfDroidsWithStepper: umbenennen, da viele Action-Methoden mit unterschiedlichen Eingabeelementen funktionieren.

Analog dazu müssen Sie natürlich auch in der Methodendeklaration in der Headerdatei den Typ des Parameters anpassen. Wenn Sie nun den Stepper betätigen, bemerken Sie allerdings noch keine Auswirkungen Ihrer Änderungen. Deswegen fügen Sie zur Implementierung des Models noch die Methode countOfObjects hinzu:

- (int)countOfObjects {
return (int)[objects count];
}

Listing 2.23 Anzahl der Objekte im Modell bereitstellen

Sie gibt die Anzahl der Objekte im Modell zurück, und Sie fügen in der Datei Model.h die entsprechende Methodendeklaration – (int)countOfObjects; hinzu. Durch diese Ergänzung können Sie nun auch im Viewcontroller auf diesen Wert zugreifen und ihn in der Action-Methode updateCountOfDroids: ausgeben.

- (IBAction)updateCountOfDroids:(UIStepper *)sender {
int theValue = [sender value];

[self.model updateDroids:theValue];
[self writeLog:[NSString stringWithFormat:
@"countOfObjects = %d", [self.model countOfObjects]]];
}

Listing 2.24 Ausgabe der Objekte im Model

Nach der Erweiterung sehen Sie im Text-View nach jedem Drücken eines Stepper-Buttons, wie viele Droiden sich im Modell befinden.


Rheinwerk Computing - Zum Seitenanfang

2.2.4VererbungZur nächsten ÜberschriftZur vorigen Überschrift

In der Einführung zur objektorientierten Programmierung in Abschnitt 2.1 haben Sie bereits das Paradigma des Vererbens kennengelernt. Sie haben auch schon Vererbung in der Beispiel-App verwendet, denn die Klasse ViewController im Projekt ist ja eine Subklasse von UIViewController, und die beiden von Ihnen selbst zum Projekt hinzugefügten Klassen Model und Droid sind Subklassen von NSObject. In allen drei Headerdateien der Klassen sehen Sie in der Klassendeklaration jeweils die Superklasse der entsprechenden Klasse. Der Name der Superklasse steht dabei jeweils in der Zeile der @interface-Direktive, durch einen Doppelpunkt vom Namen der Klasse getrennt:

@interface ViewController : UIViewController
@interface Model : NSObject
@interface Droid : NSObject

Listing 2.25 Deklaration der Klassen mit Angabe der Superklassen

Um eine Klasse von einer bestehenden Klasse abzuleiten, müssen Sie also nichts weiter tun, als die Superklasse entsprechend im Interface anzugeben. Allerdings muss dafür die Deklaration der Superklasse vorliegen. Das geschieht über den Import der Headerdatei der Superklasse. Die Klasse UIViewController deklariert die Headerdatei UIKit/UIViewController.h, die die Datei ViewController.h indirekt über die Datei UIKit/UIKit.h importiert. Über diese Headerdatei importieren Sie alle Deklarationen des UIKit-Frameworks (oder auch kurz UIKits) von Cocoa Touch, deren Elemente Sie am Präfix »UI« erkennen.

Die Klasse NSObject gehört hingegen zum Foundation-Framework, und deshalb importieren Model.h und Droid.h jeweils die Datei Foundation/Foundation.h. Die Grundlagen dieses Frameworks stammen noch aus NeXTStep-Zeiten, weswegen seine Elemente das Präfix »NS« aufweisen.

Spitze Klammern oder Anführungszeichen?

Vielleicht ist Ihnen aufgefallen, dass diese Importanweisungen spitze Klammern anstatt Anführungszeichen verwenden. Darüber teilen Sie dem Compiler mit, dass er diese Dateien nicht im Projekt, sondern in den Systemverzeichnissen suchen soll. Die Headerdateien des Projekts binden Sie also mit Anführungszeichen ein, zum Beispiel:

#import <Foundation/Foundation.h>
#import "Model.h"

Als Nächstes fügen Sie dem Projekt eine neue Klasse mit einer Superklasse hinzu, die Sie selbst festlegen. Dazu verwenden Sie allerdings keine Vorlage aus der Bibliothek, sondern rufen wieder das Xcode-Menü FileNewFile... auf. Im ersten Dialogfenster wählen Sie in der linken Spalte den Punkt iOSCocoa Touch und danach im rechten Bereich die Vorlage Objective-C class (siehe Abbildung 2.23).

Im folgenden Dialog geben Sie der Klasse den Namen ProtocolDroid [Anm.: Neben schlechtem Humor zeichnen sich Informatiker durch eine Vorliebe für Star Wars aus.] und wählen als Superklasse die Klasse Droid aus, wie Abbildung 2.24 zeigt. Nach Beendigung des Dialogs wählen Sie noch den Ablageort der Dateien, an dem Xcode danach die Interface- und die Headerdatei anlegt.

Abbildung

Abbildung 2.23 Eine neue Klasse im Projekt

Abbildung

Abbildung 2.24 Eine Klasse vom Typ »Droid«

In der Headerdatei sehen Sie, dass die neue Klasse die Superklasse Droid hat. Außerdem hat die IDE schon die Deklaration der Superklasse über die Headerdatei Droid.h importiert, die ja für die Deklaration von ProtocolDroid notwendig ist:

#import "Droid.h"
@interface ProtocolDroid : Droid
@end

Listing 2.26 Das Interface der Klasse »ProtocolDroid«

Anschließend erzeugen Sie eine weitere neue Klasse im Projekt, dieses Mal durch das Hinzufügen einer Objective-C class aus der File Template Library. Geben Sie der Klasse den Namen AstroDroid. Xcode öffnet hierbei lediglich ein Fenster, in dem Sie den Namen der Implementierungsdatei [Anm.: Dies impliziert auch den Klassennamen.] angeben können. Den Namen der Superklasse können Sie hierbei jedoch nicht angeben. Die neue Klasse stammt deswegen immer von NSObject ab, und dementsprechend sieht die Headerdatei dieser Klasse zunächst wie folgt aus:

#import <Foundation/Foundation.h>
@interface AstroDroid : NSObject
@end

Listing 2.27 Das Interface der Klasse »AstroDroid« nach dem Anlegen

Wie der Name der Klasse schon vermuten lässt, soll auch sie eine Subklasse von Droid sein. Ändern Sie daher die Superklasse in der Deklaration von NSObject auf Droid, und ersetzen Sie die #import-Anweisung durch den Import von Droid.h: [Anm.: Achten Sie dabei auch darauf, die spitzen Klammern durch Anführungszeichen zu ersetzen; Droid.h ist ja keine Headerdatei des Systems.]

#import "Droid.h"
@interface AstroDroid : Droid
@end

Listing 2.28 Die Klasse »AstroDroid« mit neuer Superklasse

Die Klasse ProtocolDroid wird zunächst keine Änderungen erfahren. Somit erbt sie alle Eigenschaften ihrer Superklasse, also von Droid. Um das Wesen der Vererbung von Eigenschaften zu zeigen, ändern Sie das Modell der Beispiel-App jetzt so, dass es neben Objekten vom Typ Droid auch Objekte vom Typ ProtocolDroid verwaltet. Fügen Sie dazu in der Implementierungsdatei Model.m die Anweisungen

#import "Model.h"
#import "Droid.h"
#import
"ProtocolDroid.h"
#import
"AstroDroid.h"

Listing 2.29 Erweiterung der Importanweisungen im Modell

hinzu, um der Implementierung der Klasse Model die Klassen ProtocolDroid und AstroDroid bekannt zu machen. Anschließend erweitern Sie die Methode updateDroids: in dieser Datei um die hervorgehobenen Zeilen:

- (void)updateDroids:(int)inValue {
if(inValue > [objects count]) {
int theRemainder = inValue % 3;
Droid *theDroid;

if(theRemainder == 0) {
theDroid = [[Droid alloc] initWithID:inValue];
}
else if(theRemainder == 1) {
theDroid = [[ProtocolDroid alloc]
initWithID:inValue];
}
else {
theDroid = [[AstroDroid alloc]
initWithID:inValue];
}
[objects addObject:theDroid];
}
else if (inValue < [objects count]) {
[objects removeLastObject];
}
}

Listing 2.30 Anlegen von unterschiedlichen Modellobjekten

Die Methode legt nun abwechselnd Droiden aller drei Arten an, wobei sie deren Typ anhand des Parameterwerts auswählt. Dabei berechnet der Operator % den Divisionsrest von zwei Zahlen. Bei positiven Werten für inValue können hierbei nur die Werte 0, 1 und 2 herauskommen, und anhand dieses Restes wählt die Methode die Klasse aus.

Die Implementierung nutzt außerdem zwei Vorteile der Objektorientierung aus. Bislang haben Sie den Initializer nur in der Klasse Droid implementiert. Die Methode verwendet ihn dennoch für die Erzeugung von Protokoll- und Astrodroiden. Das ist möglich, da ja deren Klassen das Verhalten der Klasse Droid erben und somit auch die Methode initWithID:. Des Weiteren weist die Methode alle Droiden der gleichen Variablen theDroid vom Typ Droid zu, was Ihnen das Subtyping erlaubt. AstroDroid und ProtocolDroid sind ja Subtypen von Droid, weswegen Sie deren Objekte auch überall dort verwenden dürfen, wo Sie Droiden verwenden. Ein Protokolldroide ist ja schließlich auch ein Droide.

Um die neue Funktionalität zu überprüfen, erweitern Sie auch die Methode initWithID: in der Datei Droid.m um die im folgenden Listing hervorgehobene NSLog-Anweisung:

- (instancetype)initWithID:(int)inID {
self = [super init];
if(self != nil){
self.droidID =
[NSString stringWithFormat:@"0xDEADBEEF%i", inID];
}
NSLog(@"[+] %@.%@", self, NSStringFromSelector(_cmd));
return self;
}

Listing 2.31 Erweiterung der Initialisierung um eine Log-Anweisung

Damit können Sie dann in der Konsolenausgabe sehen, für welche Klassen das Modell die Methode initWithID: aufgerufen hat, da die Log-Anweisung für self jeweils den Klassennamen und die Speicherdresse des Objekts ausgibt. Wenn Sie die App übersetzen, starten und neue Objekte über den Plus-Button des Steppers einfügen, sehen Sie in der Konsole, dass das Modell Droiden der Klassen Droid, ProtocolDroid und AstroDroid in dieser Reihenfolge einfügt. Dazwischen steht jeweils noch eine weitere Zeile, die der Aufruf von writeLog: in der Methode updateCountOfDroids: des Viewcontrollers verursacht:

[+] <ViewController: 0x71619e0>.writeLog:
[+] <ProtocolDroid: 0x753d0c0>.initWithID:
[+] <ViewController: 0x71619e0>.writeLog:
[+] <AstroDroid: 0x753fed0>.initWithID:
[+] <ViewController: 0x71619e0>.writeLog:
[+] <Droid: 0x7585900>.initWithID:
[+] <ViewController: 0x71619e0>.writeLog:
[+] <ProtocolDroid: 0x75907b0>.initWithID:
[+] <ViewController: 0x71619e0>.writeLog:
[+] <AstroDroid: 0x75894f0>.initWithID:
[+] <ViewController: 0x71619e0>.writeLog:

Listing 2.32 Konsolenausgabe beim Einfügen von Droiden

Abbildung 2.25 stellt die Aufrufe der Initialisierungsmethoden noch einmal grafisch für die einzelnen Klassen dar. Wenn das Modell beispielsweise ein Objekt der Klasse Droid initialisiert, sendet es die Nachricht initWithID: an das Objekt. Die Methode sendet wiederum die Nachricht init, die zur Ausführung der init-Methode in NSObject führt. Wenn das Modell die initWithID:-Nachricht an einen Protokolldroiden sendet, ruft die Laufzeitumgebung den entsprechenden Initializer der Superklasse Droid auf, da die Klasse ProtocolDroid diese Methode ja nicht implementiert. [Anm.: Das ist in der Abbildung durch die graue Schriftfarbe angedeutet.] Die Klasse AstroDroid implementiert die initWithID:-Methode, weswegen diese Klasse die entsprechende Nachricht verarbeitet. Sie sendet ihrerseits die init-Nachricht, wobei die Laufzeitumgebung wegen des Empfängers super jedoch nur die Superklassen von AstroDroid, also Droid und NSObject, nach Implementierungen durchsucht. Da nur die Klasse NSObject eine Implementierung bereitstellt, führt diese Nachricht zum Aufruf dieser Methode aus NSObject.

Abbildung

Abbildung 2.25 Aufruf der Methoden über die Vererbungshierarchie

Kopieren Sie nun die Implementierung der Methode initWithID: aus der Klasse Droid in den Implementierungsblock der Datei AstroDroid.m, und verändern Sie den Formatstring für die Erzeugung der Zeichenkette für die Property droidID:

- (Typo: istancetype)initWithID:(int)inID {
self = [super init];
if(self != nil){
self.droidID =
[NSString stringWithFormat:@"0xBEEFCAFE%i", inID];
}
NSLog(@"[+] %@.%@", self, NSStringFromSelector(_cmd));
return self;
}

Listing 2.33 Astrodroiden sind anders als andere Droiden

Die Methoden initWithID: in Listing 2.31 und Listing 2.33 verwenden für den Rückgabetyp das Schlüsselwort instancetype, das besagt, dass die Methode ein Objekt der Klasse zurückliefert, und dieses Schlüsselwort ist nur für die Angabe des Rückgabetyps erlaubt. Sie können es häufig als Ersatz für den Rückgabetyp id verwenden und so dem Compiler den tatsächlichen Rückgabetyp mitteilen, ohne explizite Typumwandlungen verwenden zu müssen, was bei überschriebenen Methoden ansonsten passieren könnte. Durch die Verwendung von instancetype weiß der Compiler, dass die Methode initWithID: der Klasse Droid ein Objekt der Klasse Droid und die der Klasse AstroDroid entsprechend ein Objekt der Klasse AstroDroid liefert.

Das funktioniert sogar für die Klasse ProtocolDroid, die die Methode initWithID: nicht überschreibt, sondern von der Klasse Droid erbt; der Compiler weiß, dass der Ausdruck [[ProtocolDroid alloc] initWithID:8] ein Objekt der Klasse ProtocolDroid liefert. Zum Vergleich: Bei der Verwendung von id als Rückgabetyp würde der Compiler bei der Zuweisung

NSString *theDroid = [[ProtocolDroid alloc] initWithID:8];

keinen Fehler melden. Verwenden Sie hingegen (Droid *) als Rückgabetyp, akzeptiert der Compiler

ProtocolDroid *theDroid = 
[[ProtocolDroid alloc] initWithID:8];

nicht, und Sie müssten für diese Zuweisung die Typumwandlung

ProtocolDroid *theDroid = 
(ProtocolDroid *)[[ProtocolDroid alloc] initWithID:8];

einfügen, damit der Compiler die Zuweisung akzeptiert.

Damit Sie die neuen Kennungen auch zu sehen bekommen, fügen Sie zunächst die Methode listDroids zu der Klasse Model hinzu, wobei Sie auch die entsprechende Deklaration – (void)listDroids; in die Klassendeklaration einfügen müssen.

- (void)listDroids {
NSLog(@"[+] Current droids (%d):", [self countOfObjects]);
for(Droid *aDroid in objects) {
NSLog(@"[+] %@: %@", aDroid, aDroid.droidID);
}
}

Listing 2.34 Ausgabe aller Droiden in der Konsole

Auch bei dieser Schleife nutzen Sie wieder aus, dass die Klassen aller Objekte in dem Array von der Klasse Droid erben. Dadurch können Sie an jedes Element die Nachricht droidID senden, wobei das Listing hier die Punktnotation (also die alternative Schreibweise aDroid.droidID für Accessoren) anstelle von [aDroid droidID] verwendet.

Fast Enumeration

Listing 2.34 verwendet zur Ausgabe aller Droiden auf die Konsole eine spezielle for-Schleife, die Apple als Fast Enumeration bezeichnet. Sie verwendet keine Indexvariable und keinen indizierten Elementzugriff, sondern weist die Elemente direkt der Schleifenvariablen zu. Die dem entsprechende klassische C-Schleife sieht so aus:

int theCount = [objects count];

for(int i = 0; i < theCount; ++i) {
Droid *aDroid = [objects objectAtIndex:i];

NSLog(@"[+] %@: %@", aDroid, aDroid.droidID);
}

Danach erstellen Sie eine Action-Methode für den Button, den Sie nachträglich zum View hinzugefügt haben (siehe Abbildung 2.21). Diesmal erstellen Sie zuerst die Action-Methode, indem Sie die Methode listModel: in die Datei ViewController.m einfügen. Eine Deklaration der Methode in der Headerdatei ViewController.h ist nicht erforderlich, da Sie diese Methode nicht aus einer anderen, von Ihnen erstellten Klasse aufrufen.

- (IBAction)listModel:(id)sender {
[self.model listDroids];
}

Listing 2.35 Action-Methode mal ohne Hilfseditor

Diese Methode müssen Sie jetzt noch mit dem Button verbinden. Dazu öffnen Sie das Storyboard und ziehen bei gedrückter ctrl-Taste eine Verbindung vom Button zu dem Objekt View Controller in der Objektleiste der Datei, wie es Abbildung 2.26 zeigt.

Nach dem Loslassen öffnet sich ein Pop-up-Menü, indem Sie die soeben erstellte Action-Methode finden; wählen Sie diesen Eintrag aus (siehe Abbildung 2.27).

Abbildung

Abbildung 2.26 Verbindung zum Viewcontroller über den »File’s Owner«

Abbildung

Abbildung 2.27 Action-Verbindung erstellen

Der Button ist nun mit der Methode verbunden, und wenn Sie nach dem Start der App zunächst einige Male den Plus-Button des Steppers drücken und anschließend über den Button die Action-Methode auslösen, sollten Sie in der Konsole alle Droiden mit deren Kennungen sehen (siehe Listing 2.36):

[+] Current droids (6):
[+] <ProtocolDroid: 0x7181260>: 0xDEADBEEF1
[+] <AstroDroid: 0x759deb0>: 0xBEEFCAFE2
[+] <Droid: 0x71e1a30>: 0xDEADBEEF3
[+] <ProtocolDroid: 0x759f3a0>: 0xDEADBEEF4
[+] <AstroDroid: 0x759f040>: 0xBEEFCAFE5

Listing 2.36 Ausgabe der Droiden mit Kennung

Action-Methoden

Bei der Erstellung dieser Action haben Sie zwei Dinge gesehen: Zum einen lassen sich Action-Methoden auch ohne Hilfseditor wie gewöhnliche Methoden erstellen und über den Interface-Builder mit dem View verbinden. Zum anderen ist es nicht notwendig, dass Sie Action-Methoden in der Headerdatei deklarieren. Wenn Sie also eine Action-Methode vor dem direkten Zugriff aus anderen Objekten schützen möchten, können Sie sie auf diesem Weg verstecken.

Die Schleife versendet die Nachricht droidID ja an Objekte mit unterschiedlichem Typ. Das funktioniert hier, weil sich die entsprechende Methode in der gemeinsamen Superklasse Droid befindet. Es reicht in Objective-C im Gegensatz zu vielen anderen Programmiersprachen allerdings vollkommen aus, dass die Objekte die entsprechende Methode besitzen. Sie müssen nicht unbedingt eine gemeinsame Superklasse haben.

Um dies zu verdeutlichen, fügen Sie der Beispiel-App eine weitere Klasse mit der Superklasse NSObject hinzu. Ob Sie die Klasse über das Menü oder die File Template Library hinzufügen, ist Ihnen überlassen. Geben Sie der Klasse den Namen Wookiee, und fügen Sie eine Property name vom Typ NSString, eine Methode sayName und eine Methode initWithName: hinzu, so dass Sie die folgende Klassendeklaration erhalten:

@interface Wookiee : NSObject

@property(copy) NSString *name;

- (id)initWithName:(NSString *)inName;
- (void)sayName;

@end

Listing 2.37 Jetzt kommen Wookiees ins Spiel.

In der Implementierung fügen Sie anschließend die beiden Methoden ein. Die Methode initWithName: entspricht der Methode initWithID: aus der Klasse Droid. Die Methode sayName schreibt den Wert der Property name in die Konsole, gemeinsam mit dem aktuellen Objekt- und dem Methodennamen.

#import "Wookiee.h"

@implementation Wookiee

- (id)initWithName:(NSString *)inName {
self = [super init];
if(self != nil){
self.name = inName;
}
NSLog(@"[+] %@.%@", self, NSStringFromSelector(_cmd));
return self;
}

- (void)sayName{
NSLog(@"[+] %@.%@: %@", self, NSStringFromSelector(_cmd),
self.name);
}

@end

Listing 2.38 Implementierung der Klasse »Wookiee«

Aber nicht nur Wookiees, sondern auch die Droiden sollen ihren Namen ausgeben können. Fügen Sie deshalb die Methodendeklaration -(void)sayName; in die Headerdatei Droid.h ein, und implementieren Sie die Methode in Droid.m folgendermaßen:

- (void)sayName{
NSLog(@"[+] %@.%@: %@", self, NSStringFromSelector(_cmd),
self.droidID);
}

Listing 2.39 Auch Droiden sagen ihren Namen.

Diese Implementierung kopieren Sie auch in den Implementierungsblock der Klasse AstroDroid und ersetzen das Pluszeichen am Anfang durch ein Sternchen. Danach importieren Sie in Model.m die Deklaration der Wookiee-Klasse durch #import "Wookiee.h" und passen die Implementierung von listDroids so an, wie es Listing 2.40 zeigt:

- (void)listDroids {
Wookiee *theWookiee =
[[Wookiee alloc] initWithName:@"Chewbacca"];

[objects
addObject:theWookiee];
NSLog(@"[+] Current droids (%d):", [self countOfObjects]);
for(Droid *aDroid in objects) {
[aDroid sayName];
}
}

Listing 2.40 Wookiees mischen die Droiden auf.

Jeder Aufruf dieser Methode fügt nun ein Objekt der Klasse Wookiee mit dem Namen »Chewbacca« zu dem Array objects hinzu. Danach durchläuft die Schleife wieder alle Objekte des Arrays und sendet an jedes die Nachricht sayName. Die Konsolenausgabe des Programms für die Schleife enthält nun auch Wookiees:

[+] <Wookiee: 0x7149a20>.initWithName:
[+] Current droids (5):
[+] <ProtocolDroid: 0x71b3b30>.sayName: 0xDEADBEEF1
[*] <AstroDroid: 0x71a0660>.sayName: 0xBEEFCAFE2
[+] <Droid: 0x71c6820>.sayName: 0xDEADBEEF3
[+] <ProtocolDroid: 0x719cdf0>.sayName: 0xDEADBEEF4
[+] <Wookiee: 0x7149a20>.sayName: Chewbacca

Listing 2.41 Ein Wookiee unter Droiden

Dabei fällt auf, dass die Schleifenvariable aus Listing 2.40 zwar den Klassennamen Droid verwendet, sie anscheinend aber auch klaglos mit Objekten der Klasse Wookiee zurechtkommt, obwohl ein Wookiee kein Droide ist. Das liegt daran, dass die Laufzeitumgebung nicht prüft, ob der Variablentyp mit der Klasse des Objekts zusammenpasst. Wenn der Wookiee in der Schleife an der Reihe ist, zeigt also aDroid auf ein Objekt der Klasse Wookiee, und niemanden scheint es zu stören. Sogar der Methodenaufruf funktioniert, da ja sowohl die Droiden als auch die Wookiees die Methode sayName implementieren.

Es ist allerdings ein sehr schlechter Stil, Variablen für Objekte mit einem unpassenden Typ zu verwenden, und dem guten Chewie würde das sicherlich auch nicht gefallen. Es ist besser, hier einen passenden Typ für die Variable zu verwenden.

Die Klassen Droid und Wookiee haben die gemeinsame Superklasse NSObject. Wenn Sie die Deklaration der Schleifenvariable allerdings in NSObject *aDroid ändern, erhalten Sie für die Anweisung [aDroid sayName] die Fehlermeldung, da ja die Klasse NSObject nicht die Methode sayName implementiert. Besser ist hier die Verwendung des Typs id für die Variable, da Sie an Variablen dieses Typs jede Nachricht senden können, solange sie irgendeine bekannte Klasse deklariert. Ändern Sie also den Typ in id – und am besten auch gleich den Variablennamen in anItem, um den mächtigen Chewbacca nicht zu verärgern:

for(id anItem in objects) {
[anItem sayName];
}

Listing 2.42 Wookiee-freundliche Version der Schleife

Nachrichten versenden und verarbeiten

Wie Sie an dem Beispiel mit dem Wookiee gesehen haben, ist bei Objective-C die Klasse nicht so wichtig wie in vielen anderen Programmiersprachen. Durch diese Trennung von Nachrichtenversand und -verarbeitung ist Objective-C sehr dynamisch, da Sie bei der Implementierung nicht mehr darauf angewiesen sind, dass die Objekte bestimmte Klassen besitzen. Es reicht vollkommen aus, wenn sie über die notwendigen Methoden verfügen.


Rheinwerk Computing - Zum Seitenanfang

2.2.5KategorienZur nächsten ÜberschriftZur vorigen Überschrift

Eng mit der Vererbung verwandt sind Kategorien. Angenommen, Sie möchten eine bestehende Klasse um eine oder mehrere Methoden erweitern, die Ihnen die Arbeit mit dieser Klasse erleichtern, dann können Sie eine Subklasse erzeugen und die neue Methode in der Subklasse verwenden. Das ist allerdings bei bereits bestehenden Objekten nicht möglich, da Sie ja nicht die Klasse eines Objekts austauschen können. An dieser Stelle helfen Kategorien, über die Sie Klassen nachträglich erweitern können, ohne von ihnen ableiten zu müssen.

Mit Kategorien können Sie aber nicht nur die selbstgeschriebenen Klassen erweitern, sondern auch solche, die schon fertig vorliegen und deren Implementation Sie nicht kennen. Obendrein vererben sich ihre Methoden auch auf die Subklassen der Klasse. Sie können beispielsweise mit einer Kategorie der Klasse NSObject neue Methoden hinzufügen, und alle Subklassen erben diese neuen Methoden. Damit steht dann diese neue Methode fast allen Klassen zur Verfügung.

Als Beispiel für eine Kategorie soll die Klasse NSString die Methode reversedString erhalten, die die enthaltene Zeichenkette umgedreht zurückgibt. Eine Kategorie besitzt, wie eine Klasse, eine eigene Header- und Implementierungsdatei. Fügen Sie daher eine neue Kategorie über FileNewFile... zu dem vorhandenen Xcode-Projekt hinzu, indem Sie die Vorlage Objective-C category verwenden (siehe Abbildung 2.28).

Im folgenden Dialog geben Sie in das Feld Category den Namen »ReverseString« und unter Category on den Klassennamen »NSString« ein, wie Abbildung 2.29 zeigt.

Xcode fügt nach erfolgreicher Beendigung des Wizards die beiden Dateien NSString+ReverseString.h und NSString+ReverseString.m in Ihr Projekt ein. Die Schnittstelle der Kategorie sieht dabei einer Klassendeklaration sehr ähnlich; anstelle des Doppelpunktes und der Superklasse steht hier allerdings der Name der Kategorie in runden Klammern. Die Vorlage bindet außerdem die Headerdatei des Foundation-Frameworks mit ein, da für eine Kategoriedeklaration immer die Deklaration der Klasse bekannt sein muss, auf der sie basiert.

Abbildung

Abbildung 2.28 Vorlage für Kategorien auswählen

Abbildung

Abbildung 2.29 Eingabe des Kategorienamens und der Klasse

#import <Foundation/Foundation.h>

@interface NSString (ReverseString)

@end

Listing 2.43 Headerdatei einer Kategorie

Analog sieht die Implementierung der Kategorie aus:

#import "NSString+ReverseString.h"

@implementation NSString (ReverseString)

@end

Listing 2.44 Implementierung einer Kategorie

In der Deklaration fügen Sie nun die Deklaration für die Methode reversedString hinzu:

@interface NSString (ReverseString)

- (NSString *)reversedString;

@end

Listing 2.45 Methodendeklaration in einer Kategorie

Diese Methode können Sie danach implementieren. Allerdings benötigen Sie dafür eine Subklasse von NSString, die Sie bislang noch nicht verwendet haben. Im Gegensatz zu NSString erlaubt NSMutableString nämlich die Änderung der enthaltenen Zeichenkette nach der Initialisierung.

- (NSString *)reversedString {
int theLength = [self length];
NSMutableString *theReverse =
[[NSMutableString alloc] init];

for(int i = theLength – 1; i>=0; i--){
[theReverse appendFormat:@"%C",
[self characterAtIndex:i]];
}
return theReverse;
}

Listing 2.46 Implementierung der Kategoriemethode

Da die Kategorie eine Erweiterung der Klasse NSString ist, enthält self eine Referenz auf ein Objekt dieser Klasse, und über [self length] erhalten Sie folglich die Anzahl der enthaltenen Zeichen. Die for-Schleife geht danach die Zeichen in der Zeichenkette von hinten nach vorn durch und hängt sie an eine solche veränderliche Zeichenkette an, die sie schließlich als Ergebnis liefert. Das ist möglich, da ja NSMutableString eine Subklasse von NSString ist.

Die neue Kategoriemethode soll nun bei den Protokolldroiden zum Einsatz kommen, die ja von Haus aus immer einen etwas verwirrten Eindruck machen. Deswegen soll die Property-Methode droidID ihre Kennung verkehrt herum zurückgeben. Dafür müssen Sie die Headerdatei der Kategorie einbinden und die Methode droidID überschreiben. Auch wenn Sie in der Superklasse Droid nicht explizit eine Methode droidID geschrieben, sondern sie implizit über die gleichnamige Property erzeugt haben, können Sie diese Methode dennoch überschreiben und auch per super-Aufruf darauf zugreifen:

#import "ProtocolDroid.h"
#import "NSString+ReverseString.h"

@implementation ProtocolDroid

- (NSString *)droidID {
return [[super droidID] reversedString];
}

@end

Listing 2.47 Verwendung der Kategoriemethode

Durch diese Änderung ändert sich die Konsolenausgabe für Protokolldroiden:

[+] <ProtocolDroid: 0x7528910>.sayName: 1FEEBDAEDx0
[*] <AstroDroid: 0x7523e80>.sayName: 0xBEEFCAFE2
[+] <Droid: 0x7125650>.sayName: 0xDEADBEEF3
[+] <ProtocolDroid: 0x7523e40>.sayName: 4FEEBDAEDx0
[+] <Wookiee: 0x7528f70>.sayName: Chewbacca

Listing 2.48 Protokolldroiden geben ihre Kennung verkehrt herum aus.

Kategorien versus Subklassen

Sie sollten jedoch Kategorien nicht als Ersatz für Subklassen verwenden. Das kann zu einem unerwarteten Verhalten Ihres Codes führen. Bestehende Methoden überschreiben Sie immer über Subklassen und nicht über Kategorien.

Mit der anonymen Kategorie gibt es noch einen Sonderfall bei den Kategorien. Wie der Name schon andeutet, hat eine anonyme Kategorie keinen Namen. Stattdessen geben Sie bei der Deklaration nur ein leeres Klammerpaar an.

@interface Droid()

@end

Listing 2.49 Deklaration einer anonymen Kategorie

Die Implementierung ihrer Methoden erfolgt hier nicht in einem entsprechenden Implementierungsblock, sondern in der Implementierung der Klasse. Auch anonyme Kategorien haben den Zweck, ihre Klassen um Methoden zu erweitern. Im Gegensatz zu den benannten Kategorien sind Methoden anonymer Kategorien aber in der Regel nicht öffentlich zugänglich, und Sie deklarieren über die anonyme Kategorie meistens private Methoden einer Klasse. Außerdem gestatten anonyme Kategorien die Deklaration von synthetisierten Propertys, die dadurch ebenfalls privat sind. Aus diesem Grund erfolgt die Kategoriedeklaration auch in der Implementierungsdatei der Klasse. Sie können damit beispielsweise das Attribut objects in der Klasse Model durch eine Property ersetzen, die für andere Klassen nicht sichtbar ist:

@interface Model()

@property
(strong) NSMutableArray *objects;

@end

@implementation Model

@synthesize status;
@synthesize name;
@synthesize creation;
@synthesize
objects;

- (id)init {
self = [super init];
if (self) {
self.creation = [NSDate date];
self.objects = [[NSMutableArray alloc] init];
}
return self;
}

@end

Listing 2.50 Verwendung einer privaten Property im Model


Rheinwerk Computing - Zum Seitenanfang

2.2.6ProtokolleZur nächsten ÜberschriftZur vorigen Überschrift

Ein weiteres wichtiges Sprachmerkmal von Objective-C sind Protokolle. Protokolle bündeln Methoden, die nicht zu einer bestimmten Klasse gehören. Damit lässt sich die vermeintliche Beschränkung der fehlenden Mehrfachvererbung in Objective-C kompensieren. Um einer Klasse mehr vordeklarierte Methoden als nur von einer Superklasse hinzuzufügen, kann die Klasse über ein Protokoll weitere Methoden adaptieren. Protokolle eignen sich außerdem dazu, bestimmte Methoden bei einem Objekt vorauszusetzen, ohne dessen Klasse vorgeben zu müssen. Im Gegensatz zu Klassen und Kategorien bestehen sie jedoch immer nur aus einer Deklaration. Die Implementierung erfolgt stets in den adaptierenden Klassen.

Das Schlüsselwort @protocol kennzeichnet eine Protokolldeklaration. Sie dürfen auch Protokolle voneinander ableiten, indem Sie den Namen der Oberprotokolle in einer spitzen Klammer an den Protokollnamen in der Deklaration hängen. Das Foundation-Framework enthält neben der Klasse NSObject ein Protokoll mit dem gleichen Namen, das viele Methoden der Klasse deklariert. Sie sollten in der Regel dieses Protokoll als Oberprotokoll verwenden, sofern Sie keine anderen Protokolle dafür vorsehen.

Wenn Sie beispielsweise eine einheitliche Methode zum Laden von Daten für Ihre Klassen haben möchten, können Sie das über ein Protokoll beschreiben:

@protocol Loadable<NSObject>
@required
- (void)loadFromFile:(NSString *)inPath;
@optional
- (void)loadFromURL:(NSURL *)inURL;
@end

Listing 2.51 Deklaration eines Protokolls

Die Protokollmethoden können Sie dabei als zwingend erforderlich (@required) oder als optional (@optional) deklarieren. Erforderliche Methoden muss jede adaptierende Klasse implementieren, während Sie sich das bei optionalen Methoden aussuchen können. Jede Klasse, die das Beispielprotokoll Loadable adaptiert, muss also die Methode loadFromFile: implementieren. Standardmäßig sind alle Protokollmethoden erforderlich, wenn Sie diese Angaben weglassen.

Sie verwenden ein Protokoll, indem Sie es in spitzen Klammern hinter der Superklasse in der Klassendeklaration angeben.

@interface Droid : NSObject<Loadable>

@end

Listing 2.52 Adaption eines Protokolls durch eine Klasse

Sie können natürlich auch mehrere Protokolle in einer Klasse implementieren. In diesem Fall trennen Sie die Namen der Protokolle in den spitzen Klammern einfach durch jeweils ein Komma. Es ist auch möglich, Protokolle über Kategorien zu implementieren. Hierfür platzieren Sie die spitzen Klammern einfach hinter den runden Klammern in der Kategoriedeklaration:

@interface Droid(Loading)<Loadable>

@end

Listing 2.53 Adaption eines Protokolls über eine Kategorie

Das funktioniert natürlich auch mit mehreren Protokollen und auch bei anonymen Kategorien.


Rheinwerk Computing - Zum Seitenanfang

2.2.7VorwärtsdeklarationenZur nächsten ÜberschriftZur vorigen Überschrift

Viele Protokolldeklarationen befinden sich in den Headerdateien von Klassen, weil sie mit den entsprechenden Klassen eng verwoben sind. Dabei entsteht häufig das Problem, dass die Klasse das Protokoll und das Protokoll die Klasse kennen muss. Diese zyklische Abhängigkeit kann auch zwischen Klassen entstehen, beispielsweise wenn jeder Droide auf einen Protokolldroiden verweisen soll:

@interface Droid : NSObject

@property(copy) NSString *droidID;
@property(weak) ProtocolDroid *protocolDroid;

- (id)initWithID:(int)inDroidID;
- (void)sayName;

@end

Listing 2.54 Zyklische Abhängigkeit von Klassen

Bei der Klassendeklaration in Listing 2.54 verweist jeder Droide auf einen Protokolldroiden, dessen Klasse ihrerseits von Droid abgeleitet ist. Diese Beziehungen zwischen den zwei Klassen stellt auch Abbildung 2.30 grafisch dar. Dabei ist ja die Vererbungsbeziehung von der Klasse ProtocolDroid zu Droid unproblematisch, da Sie sie ja bereits implementiert und getestet haben. Wenn Sie nun jedoch versuchen, über die Anweisung #import "ProtocolDroid.h" die Klasse ProtocolDroid in der Headerdatei Droid.h bekannt zu machen, erhalten Sie spätestens beim Übersetzen des Projekts diese Fehlermeldung:

cannot find interface declaration for 'Droid', superclass of 'ProtocolDroid'

Abbildung

Abbildung 2.30 Zyklische Abhängigkeit zwischen zwei Klassen

Der Compiler kann die Klassen eben nur nacheinander verarbeiten, und so kommt er bei dieser zyklischen Referenz durcheinander. Dieses Dilemma lässt sich also nicht durch eine weitere Importanweisung auflösen. Die Lösung besteht nun in der Tatsache, dass nicht beide Klassen voneinander erben (können) und die Klasse Droid nicht die komplette Deklaration der Klasse ProtocolDroid kennen muss. Eine Vorwärtsdeklaration für ProtocolDroid vor der Klassendeklaration von Droid reicht hier aus:

@class ProtocolDroid;

@implementation Droid : NSObject

Listing 2.55 Vorwärtsdeklaration für die Klasse »ProtocolDroid«

Über das Schlüsselwort @class, gefolgt von einem Klassennamen, teilen Sie dem Compiler mit, dass es die Klasse gibt, dass aber ihr genaues Aussehen noch nicht bekannt ist. Analog können Sie über das Schlüsselwort @protocol Vorwärtsdeklarationen für Protokolle angeben:

@protocol Loadable;

Listing 2.56 Vorwärtsdeklaration für ein Protokoll

Vorwärtsdeklarationen eignen sich allerdings nur für Klassen und Protokolle, die Sie in Attribut-, Property- oder Methodendeklarationen verwenden. Für Vererbungsbeziehungen in Klassen-, Protokoll- und Kategoriedeklarationen müssen Sie hingegen immer die entsprechende Deklaration importieren.


Rheinwerk Computing - Zum Seitenanfang

2.2.8Kommunikation zwischen den SchichtenZur nächsten ÜberschriftZur vorigen Überschrift

Bei größeren Applikationen können mehrere Controller das Modell verwalten. Dabei kann es vorkommen, dass ein Controller Teile des Modells ändert, die ein anderer Controller gerade anzeigt. Letzterer kann natürlich ständig prüfen, ob dieser Fall eingetreten ist, um so die Anzeige immer aktuell zu halten. Auch wenn das Nachfragen eine höfliche Kommunikationsform ist, gehört es in der Programmierung nicht zum guten Ton. Zum einen führt ständiges Fragen, auch Polling genannt, zu einer unnötigen Systembelastung, und zum anderen ist – je nach Nachfrageintervall – eine zeitnahe Informationsübermittlung nicht sichergestellt. Findet eine Zustandsänderung im Modell zwischen zwei Intervallen des Controllers statt, bleibt die Änderung bis zur nächsten Abfrage unbemerkt.

In Cocoa Touch ermöglicht es das Key-Value-Observing (KVO), dass ein Objekt automatisch andere Objekte bei Zustandsänderungen benachrichtigt. Das ist die Umsetzung des Beobachtermusters. Das beobachtete Objekt informiert seinen Beobachter dann selbständig und unmittelbar, wenn sich sein Zustand ändert.

Durch KVO können Sie es also dem Controller ermöglichen, sich über Zustandsänderungen im Modell informieren zu lassen. Dabei braucht das Modell sogar nur zu wissen, dass es Objekte gibt, die sich für die Zustandsänderung interessieren, aber nicht, welche Klassen sie haben. Und so erzeugt das Beobachtermuster dabei keine Abhängigkeit des Modells vom Controller, da die Modellobjekte die Klassen ihrer Beobachter nicht kennen. Diese Verbindung nennt man auch lose Kopplung.

Lose Kopplung

Die lose Kopplung des Modells an den Controller hat die schöne Eigenschaft, dass Sie das Modell auch ohne den Controller verwenden können. Im besten Fall können Sie es beispielsweise in eine App oder ein Kommandozeilenprogramm für den Mac einbauen, oder Sie schreiben automatisierte Testfälle, die prüfen, ob sich Ihr Modell auch wie vorgesehen verhält.

KVO ist allerdings nicht nur auf Modell- und Controllerklassen beschränkt, sondern Sie können es für beliebige Klassen mit der Superklasse NSObject verwenden oder für Klassen, die das Protokoll NSKeyValueObserving implementieren.

Über die Methode addObserver:forKeyPath:options:context: können Sie einen neuen Beobachter mit einem Zugriffspfad registrieren und ihn über removeObserver:forKeyPath: wieder abmelden. Wenn der Viewcontroller des Projekts beispielsweise den Wert der Property des Modells beobachten soll, können Sie ihn wie in Listing 2.57 als Beobachter registrieren. Es ist übrigens in vielen Fällen so, dass Objekte sich selbst als Beobachter an- und abmelden:

[self.model addObserver:self forKeyPath:@"status"
options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld
context:NULL];

Listing 2.57 Registrierung eines Beobachters

Nach dieser Registrierung benachrichtigt das Modell den Beobachter bei jeder Änderung des Wertes der Property. Die beiden Konstanten für den options-Parameter geben dabei an, dass die Benachrichtigung immer den alten und den neuen Wert enthalten soll. Für den Empfang der Benachrichtigung muss nun der Empfänger die Methode observeValueForKeyPath:ofObject:change:context: implementieren.

Die Attributwerte enthält dabei das Dictionary change. Sie können die Methode folgendermaßen implementieren:

- (void) observeValueForKeyPath:(NSString *)inKeyPath 
ofObject:(id)inObject change:(NSDictionary *)inChange
context:(void *)inContext {
if([inKeyPath isEqualToString:@"status"]) {
NSLog(@"[+] Old status:%@",
[inChange valueForKey:NSKeyValueChangeOldKey]);
NSLog(@"[+] New status:%@",
[inChange valueForKey:NSKeyValueChangeNewKey]);

}
}

Listing 2.58 Implementierung einer Observierungsmethode

Die Registrierung aus Listing 2.57 registriert den Beobachter nur für genau ein Objekt-Attribut-Paar. Wenn Sie weitere Objekte oder Attribute beobachten möchten, müssen Sie sie gesondert registrieren. Vor der endgültigen Freigabe eines Beobachters müssen Sie ihn immer abmelden. Ansonsten erhalten Sie einen Laufzeitfehler, und Ihr Programm stürzt ab. Für dieses Beispiel sieht der entsprechende Aufruf folgendermaßen aus:

[self.model removeObserver:self forKeyPath:@"status"];

Listing 2.59 Abmelden eines Beobachters

Um Key-Value-Observing auszuprobieren, kopieren Sie die Methode aus Listing 2.58 in die Implementierung der Klasse ViewController und fügen die Registrierung aus Listing 2.57 in die Methode viewDidAppear: ein.

- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
[self.model addObserver:self forKeyPath:@"status"
options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld
context:NULL];
}

Listing 2.60 Der Viewcontroller registriert sich als Beobachter ...

Außerdem sollte sich der Viewcontroller in der Methode viewWillDisappear: wieder als Beobachter des Modells abmelden:

- (void)viewWillDisappear:(BOOL)inAnimated {
[self.model removeObserver:self forKeyPath:@"status"];
[super viewWillDisappear:inAnimated];
}

Listing 2.61 ... und meldet sich auch brav wieder ab.

Damit nun der Beobachter zum Zuge kommt, muss das Model den Wert seiner Property verändern. Dazu fügen Sie am Ende des ersten if-Blocks in der Methode updateDroids: der Klasse Model die hervorgehobene Zeile aus Listing 2.62 ein.

else {
theDroid = [[AstroDroid alloc] initWithID:inValue];
}
self.status
= theDroid.droidID;
[objects addObject:theDroid];

Listing 2.62 Änderung im Modell

Wenn Sie nun nach dem Start der App zweimal den Plus-Button des Steppers drücken, finden Sie folgende Einträge in der Konsolenausgabe:

[+] <ProtocolDroid: 0x719c8d0>.initWithID:
[+] Old status:<null>
[+] New status:1FEEBDAEDx0
[+] <ViewController: 0x718d850>.writeLog:
[+] <AstroDroid: 0x7187220>.initWithID:
[+] Old status:1FEEBDAEDx0
[+] New status:0xBEEFCAFE2
[+] <ViewController: 0x718d850>.writeLog:

Listing 2.63 Ausgabe der Statusänderungen durch KVO

Jedes Mal, wenn Sie der Property status einen neuen Wert zuweisen, benachrichtigt das Modell jetzt den Viewcontroller und teilt ihm mit, was sich geändert hat. Sie haben mit KVO also ein mächtiges und elegantes Werkzeug, um Änderungen am Modell mitzubekommen. Außerdem vermeiden Sie darüber Abhängigkeiten vom Modell zum Controller, da das Modell ja dessen Klassen nicht kennt.

Aber KVO kann noch mehr: Sie können damit auch Werte beobachten, die es nicht gibt. Um das zu illustrieren, soll das Label neben dem Stepper die Anzahl der Objekte im Modell anzeigen. Die Xcode-Vorlage für die ViewController-Klasse hat bereits eine anonyme Kategorie für die Klasse angelegt. Dort fügen Sie die Deklaration der Property countLabel hinzu, und in den Implementierungsblock fügen Sie die entsprechende @synthesize-Anweisung ein:

@interface ViewController()

@property
(strong, nonatomic) IBOutlet UILabel *countLabel;

@end

Listing 2.64 Deklaration einer privaten Property

Durch den Bezeichner IBOutlet vor dem Klassennamen UILabel erlauben Sie dem Interface-Builder, diese Property mit einem Label im View zu verbinden. Öffnen Sie also das Storyboard im Interface-Builder, und öffnen Sie dann den Verbindungsinspektor des Viewcontrollers, indem Sie ihn auswählen und die Tastenkombination alt+cmd+6 drücken. In dem Inspektor finden Sie den Eintrag countLabel mit einem leeren Kreis rechts daneben. Klicken Sie mit der Maus in den Kreis, und ziehen Sie eine Verbindung zum Label auf dem View, wie Abbildung 2.31 das zeigt.

Als Nächstes registrieren Sie einen weiteren Beobachter auf den Schlüssel objects beim Modell und melden ihn auch entsprechend ab:

- (void)viewDidAppear:(BOOL)inAnimated {
[super viewDidAppear:inAnimated];
[self.model addObserver:self forKeyPath:@"status"
options:NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld
context:NULL];
[self.model addObserver:self forKeyPath:@"countOfObjects"
options:NSKeyValueObservingOptionNew context:NULL];
}

- (void)viewWillDisappear:(BOOL)inAnimated {
[self.model removeObserver:self forKeyPath:@"status"];
[self.model removeObserver:self forKeyPath:
@"countOfObjects"];
[super viewWillDisappear:inAnimated];
}

Listing 2.65 Registrierung und Abmeldung eines weiteren Beobachters

Abbildung

Abbildung 2.31 Outlet-Verbindung über den Verbindungsinspektor erstellen

Außerdem erweitern Sie die Methode observeValueForKeyPath:ofObject:change:context: folgendermaßen:

if([inKeyPath isEqualToString:@"status"]) {
NSLog(@"[+] Old status:%@",
[inChange valueForKey:NSKeyValueChangeOldKey]);
NSLog(@"[+] New status:%@",
[inChange valueForKey:NSKeyValueChangeNewKey]);
}
else
if([inKeyPath isEqualToString:@"countOfObjects"]) {
[self.countLabel setText:[NSString stringWithFormat:@"%d",
[inObject countOfObjects]]];
}

Listing 2.66 Anzeige der Anzahl im Label

Wenn Sie nun das Projekt ausführen und den Plus-Button des Steppers drücken, bleibt der Wert des Labels gleich, obwohl sich die Anzahl der Objekte im Array ändert. Das Key-Value-Observing bekommt diese Änderung allerdings nicht mit, weil es ja keinen entsprechenden Setter setCountOfObjects: im Model für diesen Wert gibt, den es beobachten kann.

Sie können jedoch Cocoa Touch vorgaukeln, dass Ihre App diesen Setter aufgerufen hat, indem Sie die Nachrichten willChangeValueForKey: und didChangeValueForKey: jeweils vor beziehungsweise nach der entsprechenden Wertänderung an das Modell senden. Damit Cocoa Touch nicht durcheinanderkommt, ist es übrigens wichtig, dass Sie immer diese beiden Methoden in dieser Reihenfolge aufrufen. Da nur die Methode updateDroids: des Modells das Array ändert, können Sie die Aufrufe für diese beiden Methoden dort unterbringen.

- (void)updateDroids:(int)inValue {
[self willChangeValueForKey:@"countOfObjects"];
if(inValue > [self.objects count]) {
...
[self.objects addObject:theDroid];
}
else if (inValue < [self.objects count]) {
[self.objects removeLastObject];
}
[self didChangeValueForKey:@"countOfObjects"];
}

Listing 2.67 Manuelles Versenden von Observierungsnachrichten

Nach dieser Änderung empfängt der Viewcontroller auch Observierungsnachrichten für den Schlüssel countOfObjects, und er passt den Wert nach einem Drücken des Plus- oder Minus-Buttons entsprechend an.

KVO-Nachrichten manuell versenden

Die Verwendung dieser beiden Methoden hier ist allerdings ein Ausnahmefall, und wenn das Modell das Array an mehreren Stellen verändern würde, wäre dieses Vorgehen auch sehr unpraktisch. Sie sollten willChangeValueForKey: und didChangeValueForKey: nur dann verwenden, wenn es sich nicht umgehen lässt.


Rheinwerk Computing - Zum Seitenanfang

2.2.9DelegationZur nächsten ÜberschriftZur vorigen Überschrift

Delegation ist ein zentrales Entwurfsmuster von Cocoa Touch, das sehr viele Klassen verwenden und das an vielen Stellen Vererbung ersetzt. Das klingt verwunderlich: Zunächst tippen wir uns die Finger wund, um Ihnen die Vorteile der Vererbung schmackhaft zu machen, und dann ist es auf einmal gut, sie zu vermeiden?

Die Lösung dieses scheinbaren Widerspruchs ist der Umstand, dass Sie das Mittel des Erbens nur einmal pro Klasse einsetzen können. Es ist ein Joker, den Sie nur einmal ausspielen können. Wie die Astronauten sagen: »Allzu viel ist ungesund« – und so ist auch die übermäßige Verwendung der Vererbung ein Designfehler.

Viele dieser Probleme lassen sich allerdings umgehen, wenn Sie die Möglichkeiten objektorientierter Sprachen ausnutzen, ohne dabei alles auf die Karte Vererbung zu setzen. Ein Entwurfsmuster, um die Funktionalitäten von Objekten anzupassen und zu erweitern, ist die Delegation. Ihr Prinzip ist dem vieler Vorgesetzter abgeschaut: Den täglichen Kleinkram, den sie selbst nicht erledigen wollen oder können, geben sie einfach an andere weiter.

In der iOS-Programmierung funktioniert das ganz ähnlich. Eine Klasse lässt bestimmte Implementierungsdetails offen und delegiert sie an ein anderes Objekt, das Delegate, das sich um diesen Kleinkram kümmert.

Für diesen Zweck besitzen viele Klassen eine Property delegate, die auf ein Delegate verweisen kann. Das Objekt, das auf das Delegate zeigt, nennt man dann das delegierende Objekt. Die Delegate-Methoden sind ereignisgesteuert. Das heißt, wenn das delegierende Objekt ein bestimmtes Ereignis durchläuft, ruft es die entsprechenden Methoden im Delegate auf – sofern es diese implementiert.

Die Beispiel-App verwendet bereits Delegation. Wenn Sie das Projekt in Xcode betrachten, sehen Sie, dass es eine Klasse AppDelegate besitzt:

Abbildung

Abbildung 2.32 Das »AppDelegate« der Beispiel-App

Das AppDelegate ist das Delegate eines Objekts der Klasse UIApplication, das die Applikation repräsentiert. Es ist das Application-Delegate der Beispiel-App. Sobald die Applikation ein Ereignis registriert, informiert es sein Delegate – im vorliegenden Beispiel also ein Objekt der Klasse AppDelegate – über dieses Ereignis. Zu diesen Ereignissen gehören beispielsweise alle, die den Lebenszyklus der Applikation betreffen.

Cocoa Touch verwendet für die meisten Delegate-Methoden ein einheitliches Muster. Der Methodenname beginnt in der Regel mit dem Namen der delegierenden Klasse, und mit dem ersten Parameter wird das delegierende Objekt übergeben. Typische Vertreter finden Sie in der Klasse AppDelegate der Beispiel-App:

- (BOOL)application:(UIApplication *)inApplication 
didFinishLaunchingWithOptions:(NSDictionary *)inOptions

Delegate-Methoden und ihre Namen

Viele Delegate-Methoden enthalten im Namen die Hilfsverben »should«, »will« oder »did«, die Auskunft über die Funktion der Methode geben. Über Methoden mit dem Namensbestandteil »should« fragt das delegierende Objekt sein Delegate, ob es eine bestimmte Operation ausführen soll. Diese Methoden sollten dabei in der Regel nur auf die gestellte Frage antworten und keine weitergehende Programmlogik enthalten. Die Namensbestandteile »will« oder »did« enthalten Methoden, deren Ausführung vor beziehungsweise nach einem bestimmten Ereignis erfolgt. Im Gegensatz zu den Methoden mit dem Bestandteil »should« sind diese Methoden dafür gedacht, Aktionen zu bestimmten Programmereignissen auszuführen.

Abbildung 2.33 stellt die Beziehung zwischen der delegierenden Klasse UITabBar und ihrem Delegateprotokoll UITabBarDelegate grafisch dar. So löst beispielsweise der Aufruf der Methode setSelectedItem: in der Tabbar einen Aufruf der Delegate-Methode tabBar:didSelectItem: aus. Sie können die Delegation hier auch als Trennung der Zuständigkeiten sehen: Die Tabbar kümmert sich um die Darstellung und nimmt die Nutzereingaben entgegen, während das Delegate sich um die Auswertung kümmert.

Abbildung

Abbildung 2.33 Ein Delegate implementiert Methoden des Delegierenden.

Singletons

Die Klasse des Applikationsobjekts basiert auf einem weiteren Entwurfsmuster namens Singleton, das nur ein Objekt dieser Klasse im gesamten Programm zulässt. Sie können auf dieses Objekt jederzeit über die Klassenmethode sharedApplication zugreifen.

Die Klasse AppDelegate implementiert das Protokoll UIApplicationDelegate, was Sie an der entsprechenden Zeile in der Headerdatei des Delegates erkennen:

@interface AppDelegate : UIResponder<UIApplicationDelegate>

Dabei deklariert das Protokoll alle verfügbaren Methoden als optional. Trotzdem sollte eine Applikation zumindest die wichtigsten Nachrichten abfangen, da sie ansonsten nicht auf wichtige Zustandsänderungen reagieren kann.

Eine der wichtigsten Delegate-Methoden des Application-Delegates ist application:didFinishLaunchingWithOptions: [Anm.: Sie bemerken das Hilfsverb »did« im Methodennamen?] . Diese Methode ruft das Applikationsobjekt auf, nachdem das Betriebssystem die App gestartet hat. Die Projektvorlage hat diese Methode bereits implementiert:

- (BOOL)application:(UIApplication *)application 
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
return YES;
}

Listing 2.68 Startmethode des App-Delegates

Die Methode gibt nur den Wert YES zurück.

Abbildung 2.34 stellt alle Delegate-Methoden der Applikation zusammen mit den drei Applikationszuständen dar, wobei die gestrichelten Pfeile die Aufrufreihenfolge der Methoden zwischen den Zuständen angeben. Die durchgezogenen Pfeile zeigen die Zustandsübergänge an. Wenn die Applikation application:didFinishLaunchingWithOptions: aufruft, ist sie noch inaktiv. Beim Übergang in den aktiven Zustand sendet sie dann die Nachricht applicationBecomeActive:. Wenn Sie eine Applikation über den Home-Button des Geräts in den Hintergrund schicken, wechselt die Applikation in den Zustand Background.

Abbildung

Abbildung 2.34 Die Delegate-Methoden der Applikation

Sichern und Aufräumen

Sie können die Methoden applicationDidEnterBackground: und applicationWillTerminate: implementieren, um eventuell noch nicht gespeicherte Daten Ihrer App zu sichern. Außerdem können Sie diese Methoden für Aufräumarbeiten nutzen. Wenn Sie Ihr Programm in den Hintergrund schicken, sollten Sie immer so viel Speicher wie möglich freigeben. Auch während der Hintergrundausführung kann Ihr Programm noch Speicherwarnungen von Cocoa Touch empfangen.

Während es im Hintergrund ausgeführt wird, kann ein iOS-Programm nicht beliebig viel Rechenzeit in Anspruch nehmen. Sie dürfen in den Methoden applicationDidEnterBackground: und applicationWillTerminate: auch nicht einfach lange und aufwendige Operationen durchführen. Sie haben innerhalb dieser Methoden ungefähr fünf Sekunden Zeit, um notwendige Aufgaben auszuführen. Falls Ihre Implementierungen länger brauchen, wirft iOS Ihre App einfach aus dem Speicher.

In größeren Apps kann es sehr umständlich sein, alle Aufräumaktionen zentral vom Application-Delegate aus zu steuern – besonders wenn viele Viewcontroller entsprechende Operationen durchführen müssen. In diesen Fällen sollte jede Komponente selbst aufräumen. Viewcontroller sollten ihre Daten und verwendeten Ressourcen möglichst früh freigeben. Hierzu bieten sich beispielsweise die Methoden viewWillDisappear:, viewDidDisappear: und didReceiveMemoryWarning der Klasse UIViewController an.

Parallel zu den Delegate-Methoden sendet die Applikation indes auch entsprechende Benachrichtigungen (auch Notifications genannt). Das sind programminterne Nachrichten, die beliebige Objekte empfangen können. Die Namen der entsprechenden Benachrichtigungen finden Sie in der Apple-Dokumentation des Protokolls UIApplicationDelegate. Eine genauere Beschreibung von Notifications finden Sie in Kapitel 5, »Daten, Tabellen und Controller«.

Sie können das Verwenden von Delegate-Methoden im vorliegenden Beispiel selbst testen. Fügen Sie in der Implementierungsdatei AppDelegate.m eine Implementierung für die Methode applicationDidReceiveMemoryWarning hinzu:

- (void)applicationDidReceiveMemoryWarning: 
(UIApplication *)inApplication {
NSLog(@"%@.%@ Einen Eimer für Monsieur!", self,
NSStringFromSelector(_cmd));
}

Listing 2.69 Ich bin der finstere Sensemann.

Diese Methode ruft das iOS bei knappem Hauptspeicher auf und ist eine Aufforderung, umgehend Ressourcen freizugeben. Wenn Sie dieser freundlichen Aufforderung nicht umgehend nachkommen, beendet das Betriebssystem gnadenlos Ihr Programm. Die Implementierung besteht in diesem Fall lediglich aus einer Konsolenmeldung; in der Praxis ist das natürlich nicht die angemessene Art, auf knappen Speicher zu reagieren.

Wenn Sie die App übersetzen und starten und danach im Simulator den Menüpunkt HardwareSpeicherwarnhinweis simulieren aufrufen, der eine Speicherwarnmeldung erzeugt, können Sie folgende Ausgabe der Konsole beobachten:

Kapitel2[19738:f803] Received memory warning.
<AppDelegate:0x6a17cb0>.applicationDidReceiveMemoryWarning:
Einen Eimer für Monsieur!

Listing 2.70 Das war wohl ein Pfefferminzblättchen zu viel.

Et voilà, die Applikation ruft die im Delegate implementierte Methode des UIApplicationDelegate-Protokolls korrekt auf, und das ganz ohne Vererbung.

Im weiteren Verlauf des Buches werden Sie noch weitere Beispiele für die Delegation sehen. Die »passive« Verwendung, also die Implementierung eines Delegates mit den gewünschten Methoden, ist einfach – Sie haben ja gerade auch die Delegate-Methode applicationDidReceiveMemoryWarning: im Application-Delegate selbst implementiert.

Delegation vs. Kategorien

Wir haben im Abschnitt über Kategorien bereits vollmundig erklärt, dass Kategorien das Problem tiefer Vererbungshierarchien lösen. Das Gleiche haben wir Ihnen jetzt zum Thema Delegates auch erzählt. Inzwischen glauben Sie uns wahrscheinlich nichts mehr; wir bleiben jedoch hartnäckig. Delegates und Kategorien eignen sich zwar beide zur Vermeidung von Vererbung, sie sind jedoch nicht wesensverwandt oder gar austauschbar.

Kategorien verwenden Sie, um Eigenschaften von Klassen zu ändern. Damit gewinnen Sie keine Informationen über Ereignisse und Zustandsänderungen. Mit Delegates hingegen ändern Sie keine Eigenschaften. Delegates sind ein Mittel der Kommunikation und informieren über Zustandsänderungen und Ereignisse.

Der umgekehrte Weg, selbst eine Klasse zu erstellen, die Delegate-Methoden in einem Delegate aufrufen kann, ist etwas aufwendiger. Sie müssen dazu eine Klasse mit der gewünschten Funktionalität sowie ein Protokoll implementieren. Am einfachsten lässt sich das an einem Beispiel erklären.

Die Beispiel-App erhält nun eine neue Klasse Log, mit der sich Nachrichten loggen lassen. Das Logging erfolgt über eine Methode, die als Parameter die zu loggende Nachricht erhält. Über den Delegation-Mechanismus informiert die Klasse ihr Delegate, wenn der Log-Vorgang beendet ist. Das ist in der ersten Ausbaustufe natürlich keine umwerfende Funktionalität, jedoch steht genauso wie beim bisherigen Aufbau der Beispiel-App das Prinzip und nicht die Funktion im Vordergrund.

Der Viewcontroller verwendet die Klasse Log, um Nachrichten in die Konsole zu schreiben. Die Methode hierzu trägt den Namen logToConsole: und ruft ihrerseits die Delegate-Methode logDidFinishLogging: auf.

Um diese Funktionalität abzubilden, muss das Log ein Delegateprotokoll definieren, über eine Property verfügen, die den Verweis auf das Delegate hält, und die Methode logToConsole: implementieren. Da der Viewcontroller das Delegate ist, muss er das Protokoll implementieren. Außerdem erzeugt er das Log und sendet diesem die zu auszugebenden Meldungen.

Öffnen Sie die Beispiel-App, und fügen Sie eine neue Objective-C-Klasse hinzu. Geben Sie der Klasse den Namen »Log« und die Superklasse NSObject. Die Headerdatei Log.h enthält sowohl das besagte Protokoll als auch die Klassendeklaration selbst. Die zyklische Abhängigkeit zwischen dem Protokoll und der Klasse löst hier eine Vorwärtsdeklaration des Protokolls auf.

#import <Foundation/Foundation.h>

@protocol LogDelegate;

@interface Log : NSObject

@property (nonatomic, weak) id<LogDelegate> delegate;

- (void)logToConsole:(NSString *)theMessage;

@end

@protocol LogDelegate<NSObject>

- (void)logDidFinishLogging:(Log *)inLog;

@end

Listing 2.71 Klasse und Delegateprotokoll für das Loggen

Die Protokolldeklaration enthält den Namen und die Superklasse des Protokolls (analog zu Abschnitt 2.2.6, »Protokolle«) sowie die Methode, die eine Klasse implementieren muss, um dem Protokoll zu genügen. Da diese nicht mit dem Schlüsselwort @optional versehen ist, muss das Delegate diese Methode implementieren. Der Compiler weist Sie freundlicherweise darauf hin, wenn eine Klasse eine erforderliche Methode eines Protokolls nicht implementiert.

Die Log-Klasse enthält nur die Property delegate vom Typ id<LogDelegate>. Den Typ id kennen Sie ja bereits, und der angehängte Protokollname in spitzen Klammern gibt an, dass Sie dieser Property nur Objekte zuweisen dürfen, die das Protokoll LogDelegate implementieren.

In der Datei Log.m implementieren Sie die Methode logToConsole:

#import "Log.h"

@implementation Log

- (void)logToConsole:(NSString *)theMessage {
NSLog(@"[+] %@.%@: %@", self,
NSStringFromSelector(_cmd), theMessage);
[self.delegate logDidFinishLogging:self];
}
@end

Listing 2.72 Die Implementierung der Klasse »Log«

Die NSLog-Anweisung übernimmt neben den bereits bekannten Parametern die auszugebende Meldung. Im Anschluss ruft die Methode noch die Methode logDidFinishedLogging: des Delegates auf.

Damit der Viewcontroller die neue Klasse verwenden und das Protokoll implementieren kann, müssen Sie in dessen Headerdatei ViewController.h zunächst die Datei Log.h importieren und die Deklaration der Klasse ViewController um das Protokoll LogDelegate ergänzen:

#import "Log.h"

@interface ViewController : UIViewController<LogDelegate>

Listing 2.73 Das Interface des Viewcontrollers

Da der Viewcontroller das besagte Protokoll unterstützen soll und die Methode logDidFinishLogging: erforderlich ist, fügen Sie in der Implementierung des Viewcontrollers diese Methode ein:

- (void)logDidFinishLogging:(Log *)inLog {
[self writeLog:@"Finished logging to console"];
}

Listing 2.74 Die Methode »logDidFinishLogging:« im Viewcontroller

Diese Methode macht also nichts weiter, als im Text-View auf dem GUI die Meldung auszugeben, dass das Log die Ausgabe beendet hat.

Um die Funktionalität zu testen, müssen Sie nun noch im Viewcontroller ein Log-Objekt erzeugen und diesem eine Nachricht senden. Dieses Objekt schreibt die Nachricht in die Konsole und ruft anschließend die Methode logDidFinishLogging: im Viewcontroller auf. Fügen Sie also ans Ende der Methode viewDidLoad des Viewcontrollers noch die folgenden Zeilen ein:

Log *theLog = [[Log alloc] init];
theLog.delegate = self;
[theLog logToConsole:[self.model name]];

Listing 2.75 Einbindung der Klasse »Log« im Viewcontroller

Die erste Zeile erzeugt ein Objekt der Klasse Log, und die zweite weist der Property delegate des neuen Objekts den Viewcontroller zu. Der Viewcontroller macht sich also zum Delegate des Logs. In der letzten Zeile sendet er die Methode logToConsole: an das Log-Objekt und übergibt ihr als Meldung den Namen des Modells. Testen Sie die Änderungen, und behalten Sie dabei sowohl die Konsole als auch das GUI der App im Auge.

Die Konsole zeigt pflichtgemäß die zu loggende Nachricht an:

2012-07-15 23:18:45.267 Kapitel2[78654:f803] [+] 
<Log: 0x6a5a100>.logToConsole:: LoremIpsum

Auf dem GUI erscheint, um einige Millisekunden versetzt, die Nachricht Finished logging to console, wie die Abbildung 2.35 zeigt.

Abbildung

Abbildung 2.35 Aufruf der Delegate-Methode im Viewcontroller

Wie Sie anhand der Zeitstempel sehen, ist der Aufruf der Delegate-Methode zwei Millisekunden nach dem Schreiben der Nachricht in die Konsole erfolgt. Offensichtlich stimmen also Funktionalität und Timing – ein gutes Zeichen dafür, dass die Delegation funktioniert.

Typische Anwendungsfälle für Delegation

Das Beispiel oben ist bewusst einfach gehalten und dient nur zur Veranschaulichung des Prinzips der Delegation. Delegation ist insbesondere gut geeignet, um sich über Aktivitäten informieren zu lassen, die im Hintergrund asynchron ablaufen. Netzwerkzugriffe sowie die Verarbeitung von größeren Datenmengen sind typische Beispiele. Sie müssen nicht das GUI einer App so lange blockieren, bis Sie ein großes Dokument eingelesen haben. Sie können beispielsweise das Laden im Hintergrund ablaufen lassen und sich per Delegation informieren lassen, wann dieser Auftrag beendet ist.


Rheinwerk Computing - Zum Seitenanfang

2.2.10Key-Value-CodingZur vorigen Überschrift

Den Zugriff auf die Attribute eines Objektes über Accessoren legen Sie immer zur Übersetzungszeit des Programms fest. Das Programm ruft die entsprechenden Methoden auf, um die Attributwerte zu lesen oder zu schreiben. Egal, ob Sie die Bracket- oder die Punktnotation für die Zugriffe verwenden, den Namen der Methode legen Sie dabei immer schon zur Übersetzungszeit fest.

Für bestimmte Aufgabenstellungen ist diese Art des Zugriffs allerdings sehr unpraktisch. Beispielsweise können Sie mit Core Data (siehe Kapitel 5, »Daten, Tabellen und Controller«) die Attributwerte Ihrer Objekte in einer relationalen Datenbank speichern. Dazu muss Core Data ohne hartkodierte Anweisungen auf diese Attribute zugreifen können. Es muss also an die Objekte Nachrichten der Form »Gib mir den Wert zum Attribut mit dem Namen« oder »Setze den Wert des Attributs mit dem Namen« senden, wobei der Name ein Parameter dieser Methoden ist.

Dieses Vorgehen heißt in Cocoa Key-Value-Coding (KVC), und das Protokoll NSKeyValueCoding deklariert die dazugehörenden Methoden. KVC ermöglicht es Ihnen, die Eigenschaften eines Objekts nicht über Accessoren, sondern über ihren Namen, also als Zeichenkette, anzusprechen. Dazu deklariert das Protokoll unter anderem die folgenden Methoden:

- (id)valueForKey:(NSString *)key;
- (void)setValue:(id)value forKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)key;

Listing 2.76 Methoden für das Key-Value-Coding

Mit den Methoden valueForKey: und setValue:forKey: können Sie die Attribute über ihre Namen ansprechen, wobei key der Name des Attributs ist:

NSString *theKey = ... // dynamische Daten
id theValue = [theObject valueForKey:theKey];
[theObject setValue:theNewValue forKey:theKey];

Listing 2.77 Beispiele für KVC-Methodenaufrufe

Da die Klasse NSObject das Protokoll implementiert, haben Sie automatisch KVC in Ihren Objekten zur Verfügung. Sie brauchen also für die Nutzung dieser flexiblen Möglichkeit nichts mehr zu tun – außer KVC zu nutzen. Dabei können Sie über KVC sowohl auf Accessoren (egal, ob sie synthetisiert oder selbsterstellt sind) als auch auf Attribute zugreifen, wobei die Implementierung natürlich die erste Möglichkeit bevorzugt.

Sie können auch auf Werte zugreifen, die nicht zu dem Objekt direkt, sondern zu einem referenzierten Objekt gehören. Dazu verwenden Sie die Methoden valueForKeyPath: beziehungsweise setValue:forKeyPath:, die einen Keypath verarbeiten können. Ein Keypath ist einfach eine Zeichenkette mit Attributnamen, die durch Punkte getrennt sind.

Abbildung 2.36 zeigt ein Beispiel für einen solchen Zugriff. Es liest das Gehalt von Ophelia ausgehend vom Mitarbeiter Fred über dessen Abteilung und deren Managerin. Den dazu passenden Code finden Sie in Listing 2.78. Darin setzt die letzte Anweisung Fred als den neuen Manager in der Softwareentwicklungsabteilung.

Abbildung

Abbildung 2.36 Zugriff über den Keypath »department.manager.salary«

Employee *theEmployee = [Employee employeeWithName:@"Fred"];
NSNumber *theSalary =
[theEmployee valueForKeyPath:@"department.manager.salary"];

[theEmployee setValue:theEmployee
forKeyPath:@"department.manager"];

Listing 2.78 Attributzugriffe über Pfade

Key-Value-Coding kann indes noch mehr, beispielsweise Attributwerte aufsammeln. Wenn beispielsweise der Manager eine Liste mit den Gehältern seiner Angestellten bekommen möchte, können Sie das über

NSArray *theSalaries = [theManager 
valueForKeyPath:@"department.employees.salaray"];

erreichen. Das Array theSalaries enthält nach der Ausführung NSNumber-Objekte. Der Manager will diese Liste natürlich haben, um die Summe der Gehälter auszurechnen. Anstatt jetzt mit einer Schleife die Werte mühsam zu addieren, können Sie auch direkt eine Aggregation verwenden. Das sind KVC-Operatoren, mit denen Sie mehrere Werte zu einem zusammenfassen:

NSNumber *theSum = [theManager 
valueForKeyPath:@"department.employees.@sum.salaray"];
NSNumber *theAverage = [theManager
valueForKeyPath:@"department.employees.@avg.salaray"];

Listing 2.79 Berechnung der Gehaltssumme und des Durchschnittsgehalts

Neben der Summe @sum und dem Durchschnitt @avg stehen Ihnen die Anzahl @count, das Maximum @max sowie das Minimum @min zur Verfügung. Sie können diese Operatoren allerdings nur auf Container anwenden. Also muss der Keypath bis zum Aggregat auf einen Container (z. B. NSArray, NSSet) und der Pfad dahinter auf einen skalaren Wert (z. B. NSNumber, double, NSString) verweisen. Natürlich muss der Typ des skalaren Wertes zur Operation passen. Sie können beispielsweise keinen Durchschnittswert von Zeichenketten berechnen.



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