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

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.5Das Foundation-FrameworkZur nächsten Überschrift

Sie haben bei den bisher erstellten zwei Beispiel-Apps bereits gesehen, dass Cocoa Touch eine Vielzahl von Klassen zur Verfügung stellt, die Ihre App verwendet, um die gewünschte Funktionalität herstellen zu können. Das Schreiben von Text in die Konsole mit NSLog, das Hantieren mit Strings über NSString, das Speichern von Objekten in einem Array vom Typ NSMutableArray sind Beispiele dafür, wie eine App mit den Bibliotheken von Cocoa Touch arbeitet.

Dieser Abschnitt stellt kurz die wichtigsten Klassen aus dem Foundation-Framework vor, mit denen Sie bei der iOS-Programmierung immer wieder in Berührung kommen. Sie erkennen Foundation-Klassen an dem Präfix NS, das daher rührt, dass diese Klassen bereits unter NeXTStep existierten. Obwohl das Foundation-Framework inzwischen schon über 20 Jahre alt ist, ist es jedoch keineswegs veraltet, sondern erweist sich immer noch als zeitgemäßes, mächtiges, aber dennoch schlankes Framework.

Die verschiedenen Foundation-Klassen lassen sich in zwei Arten unterteilen:

  • Es gibt Klassen für skalare Objekte, so wie die Klasse NSString, die zum Speichern eines einzelnen Wertes dienen.
  • Container beziehungsweise Collections dienen zur Verwaltung von mehreren, meist gleichartigen Objekten, wie z. B. das in der Klasse Model verwendete NSMutableArray.

Alle Klassen lassen sich wiederum in zwei Unterarten aufteilen: Immutables (unveränderliche Objekte) und Mutables (veränderliche Objekte).


Rheinwerk Computing - Zum Seitenanfang

2.5.1Mutables und ImmutablesZur nächsten ÜberschriftZur vorigen Überschrift

Mutables sind grundsätzlich Subklassen von ihren Gegenstücken für unveränderliche Objekte. Beispielsweise sind Zeichenketten der Superklasse NSString nach der Initialisierung unveränderlich, während Sie die Objekte von der Subklasse NSMutableString nach der Konstruktion noch ändern können. Tabelle 2.2 zeigt die wichtigsten Klassen für unveränderliche Objekte des Foundation-Frameworks, die Subklassen für veränderliche Objekte besitzen:

Tabelle 2.2 Immutables und Mutables des Foundation-Frameworks

Immutable Mutable

NSArray

NSMutableArray

NSAttributedString

NSMutableAttributedString

NSCharacterSet

NSMutableCharacterSet

NSData

NSMutableData

NSDictionary

NSMutableDictionary

NSIndexSet

NSMutableIndexSet

NSOrderedSet

NSMutableOrderedSet

NSSet

NSMutableSet

NSString

NSMutableString

NSURLRequest

NSMutableURLRequest

Immutable bedeutet, dass Sie in einem einmal erzeugten und mit Werten gefüllten Objekt diese Werte im Nachhinein nicht mehr ändern können. Sie finden beispielsweise in den Klassen von NSString keine Methode, mit der sich die enthaltene Zeichenkette verändern lässt; NSString ist eben immutable. Immutable bedeutet allerdings nicht, dass Sie den Wert der Referenz auf das Objekt nicht mehr ändern können.

Die Unterscheidung zwischen unveränderlichen und veränderlichen Objekten ist sehr hilfreich. Damit können Sie beispielsweise vermeiden, dass ein Programm den Zustand eines Objekts versehentlich oder absichtlich ändert, ohne Methoden aus dessen Klasse zu verwenden, und so das Geheimnisprinzip verletzt. Zum Beispiel könnte das Modell aus dem Beispielprojekt das Array mit den Droiden für andere Klassen über folgende Methode bereitstellen:

- (NSMutableArray *)droids {
return self.objects;
}

Listing 2.112 Bereitstellung der Droiden (schlechte Variante)

Dadurch lässt sich zwar kein neuer Wert an die Property objects zuweisen, aber das Einfügen eines neuen Objekts mit beliebiger Klasse ist möglich:

NSMutableArray *theDroids = [self.model droids];

[theDroids addObject:@"R2D2"];
[self.model listDroids];

Listing 2.113 Oh weh, das gibt einen Absturz.

Der Viewcontroller kann sich das Array holen und ein beliebiges Objekt dort hineinlegen. Wenn er dafür aber, wie in Listing 2.113, eine Zeichenkette verwendet, stürzt das Programm beim Aufruf von listDroids ab, da die Klasse NSString ja nicht die Nachricht sayName versteht. Ein weiterer Nachteil dieses schreibenden Zugriffs ist, dass das Key-Value-Observing für den Schlüssel countOfObjects in diesem Fall nicht mehr funktioniert.

Genau genommen ist der Code im Listing auch keine Verletzung des Geheimnisprinzips, da ja die Klasse Model das Array explizit als veränderlich deklariert. Da NSMutableArray allerdings ein Subtyp von NSArray ist, können Sie die Methode auch als – (NSArray *)droids; deklarieren und implementieren. [Anm.: Mit unverändertem Methodenrumpf] Dadurch zeigen Sie den Benutzern der Klasse Model an, dass Sie eine Veränderung des Array-Inhalts über diese Methode nicht unterstützen.

Natürlich kann es dann immer noch irgendwelche Schlaumeier geben, die den Rückgabewert als NSMutableArray erkennen, das Geheimnisprinzip ignorieren und das Objekt einfach auf NSMutableArray casten:

// in Model.m
- (NSArray *)droids {
return objects;
}
// in ViewController.m
NSMutableArray *theDroids =
(NSMutableArray *)[self.model droids];

[theDroids addObject:[[Droid alloc] initWithID:22]];
[self.model listDroids];

Listing 2.114 Ein Schlaumeier verletzt das Geheimnisprinzip.

Diese Schlaumeier nutzen also das Implementierungsdetail aus, dass die Droiden in einem veränderlichen Array liegen. Solchen Rüpeln müssen Sie natürlich einen Riegel vorschieben, indem Sie einfach eine unveränderliche Kopie des Arrays zurückgeben:

- (NSArray *)droids {
return [self.objects copy];
}

Listing 2.115 Schlaumeier-sichere Variante

Durch diese Änderung geben Sie ein unveränderliches Array zurück, und die vorletzte Zeile in Listing 2.114 führt zu einem Absturz. Sie finden übrigens die Methode copy in allen Klassen aus Tabelle 2.2, und sie liefert immer ein unveränderliches Objekt zurück. Für Mutables erzeugt sie dabei eine echte Kopie, und für Immutables gibt sie einfach eine Referenz auf das Objekt zurück.

Veränderliche Kopien

Die in Tabelle 2.2 genannten Klassen besitzen noch eine weitere Kopiermethode. Über mutableCopy erhalten Sie jeweils eine veränderliche, echte Kopie des Objekts – also niemals nur eine Referenz auf das Ursprungsobjekt.


Rheinwerk Computing - Zum Seitenanfang

2.5.2Elementare KlassenZur nächsten ÜberschriftZur vorigen Überschrift

Bei der iOS-Programmierung kommen Sie immer wieder mit elementaren Klassen in Berührung, also mit Klassen, die grundlegende Aufgaben übernehmen, wie die Repräsentation und Manipulation von Datentypen oder die Ausgabe von Informationen in die Konsole.

Wir zeigen an verschiedenen Stellen einige Code-Beispiele. Daraus eine komplette App zu erstellen, lohnt sich allerdings nicht, denn es geht an dieser Stelle nur darum, dass Sie die betreffenden Klassen kennenlernen. Die Beispiele in diesem und dem folgenden Abschnitt sollen Ihnen nur einen Eindruck von den Möglichkeiten vermitteln und Sie zum Nachschlagen in der Dokumentation anregen. Eine vollständige Dokumentation ist hier weder gewollt noch beabsichtigt, weil es sie schon gibt und Apple das auch viel besser kann.

»NSString« und »NSError«

Die Klasse NSString haben Sie bereits kennengelernt. Sie dient zum Speichern und Auswerten von Zeichenketten. Die Klasse arbeitet intern mit Unicode-Zeichen, kann also auch Umlaute und sonstige Sonderzeichen speichern. Um in Cocoa Touch ein konstantes Objekt der Klasse NSString zu erstellen, stellen Sie einfach einer entsprechenden C-Zeichenkette einen Klammeraffen @ voran:

@"In Köln gieße ich mir Wasser auf die Füße."

Diese Konstanten sind vollwertige Objekte, denen Sie alle Nachrichten schicken dürfen, die Sie an jedes andere Objekt dieser Klasse auch schicken können. Der einzige Unterschied besteht darin, dass das konstante Objekt nicht im Heap liegt, sondern dass der Compiler es im konstanten Programmspeicher anlegt, und es deshalb auch nicht zerstört.

NSString bietet noch weitere Initialisierungsmethoden. Die Methoden initWithContentsOfFile:encoding:error: und initWithContentsOfURL:encoding:error: lesen den Inhalt der Zeichenkette aus einer Datei oder über eine URL, wie die Beispiel-App aus dem ersten Kapitel. Umgekehrt können Sie über die Methode writeToFile:atomically:encoding:error: die Zeichenkette in eine Datei schreiben.

Atomares Schreiben

Über den booleschen Parameter atomically in writeToFile:atomically:encoding:error: bestimmen Sie, ob die Methode die Datei atomar schreiben soll. Atomar bedeutet dabei, dass sie die Daten entweder komplett in das Dateisystem schreibt oder gar nicht.

Das erreicht sie, indem sie die Daten zunächst in eine Zwischendatei mit anderem Namen schreibt. Nach erfolgreichem Schreiben benennt sie diese Datei einfach nur um, und wenn dabei etwas schiefgeht, löscht sie diese Zwischendatei.

Alle drei Methoden verwenden die Parameter encoding und error. Das Encoding beziehungsweise die Zeichenkodierung gibt dabei an, in welchem Zeichensatz die Daten vorliegen oder abgelegt werden sollen. Der andere Parameter ist ein Ausgabeparameter, weswegen in der Deklaration zwei Sternchen hinter dem Klassennamen stehen, z. B.:

+ (id)stringWithContentsOfFile:(NSString *)path 
encoding:(NSStringEncoding)enc error:(NSError **)error;

Listing 2.116 Deklaration des Ausgabeparameters »error«

Das ist ein Zeiger auf einen Zeiger, und Sie müssen die Speicheradresse der Variablen übergeben, die auf das Fehlerobjekt verweisen soll. Das geschieht über den Operator & wie in Listing 2.117, das eine Zeichenkette mit dem Inhalt der Datei /etc/hosts erzeugt und ausgibt.

NSError *theError = nil;
NSString *theHosts = [[NSString alloc]
initWithContentsOfFile:@"/etc/hosts"
encoding:NSASCIIStringEncoding error:&theError];

if(theError == nil) {
NSLog(@"[+] Hosts:\n%@", theHosts);
}
else {
NSLog(@"[+] Error: %@", [theError localizedDescription]);
}

Listing 2.117 Zeichenkette aus einer Datei lesen

Die Methode braucht einen Zeiger auf einen Zeiger für den Fehler, weil sie den Verweis verbiegen und nicht nur das Objekt ändern möchte. Das stellt Abbildung 2.48 grafisch dar. Die Variable theError verweist zunächst auf kein Objekt, die Adresse 0x0. Der Parameter error verweist hingegen auf diese Variable, die ihrerseits an der (fiktiven) Adresse 0x16000 steht. Da die Methode nun über den Parameter die Speicheradresse der Variablen kennt, kann sie sie auch auf ein Objekt der Klasse NSError verbiegen, das sich hier an der Adresse 0x20000 befindet.

Eine weitere wichtige Methode von NSString ist length, die die Länge der gespeicherten Zeichenkette zurückgibt. Beispielsweise liefert [@"123" length] das Ergebnis 3.

Fehlervariablen initialisieren

Sie sollten Variablen für Ausgabeparameter (wie theError) vor ihrer Verwendung immer auf nil initialisieren, da die Methode ihren Verweis nicht ändern muss. Ohne Initialisierung verweist die Variable zumindest bei manuellem Referenzzählen auf eine willkürliche Speicheradresse, und Sie haben einen Dangling Pointer, der wahrscheinlich zu einem Absturz führt.

Abbildung

Abbildung 2.48 Ein Zeiger auf einen Zeiger

Den Zugriff auf einzelne Zeichen oder Teilzeichenketten ermöglichen die Methoden characterAtIndex: und substringWithRange:, wobei Sie den Bereich über die Funktion NSMakeRange erzeugen. Dabei hat das erste Zeichen immer den Index 0. Beispielsweise liefert [@"abcdefg" characterAtIndex:2] das Zeichen c, und

[@"Blumentopferde" substringWithRange:NSMakeRange(8, 6)]

gibt die Zeichenkette pferde zurück.

Über die Methode rangeOfString: können Sie auch die Position und Länge einer Teilzeichenkette bestimmen:

NSRange theRange = 
[@"Blumentopferde" rangeOfString:@"pferde"];

Der NSRange-Wert enthält nach diesem Aufruf die Position 8 und die Länge 6.

Wenn Sie zwei Zeichenketten auf den gleichen Inhalt prüfen möchten, können Sie dafür die Methoden isEqual: oder isEqualToString: verwenden, wobei isEqual: die allgemeine Variante ist, die viele Klassen implementieren, während isEqualToString: speziell für String-Vergleiche optimiert ist. Im Gegensatz zu den Vergleichsoperatoren == und != vergleichen beide Methoden den Inhalt der Zeichenkette und nicht den Wert der Zeiger. Dazu ein paar Beispiele:

NSString *theString = @"String";
NSString *theNumber = @"8";
NSString *theDynamicString =
[NSString stringWithFormat:@"%d", 8];

if([theString isEqualToString:theNumber]) {} // NO
if([theString isEqual:theDynamicNumber]) {} // NO
if([theNumber isEqualToString:theDynamicNumber]) {} // YES
if([theString == theNumber]) {} // NO
if([theDynamicNumber == theNumber]) {} // NO

Listing 2.118 Vergleiche von Zeichenketten

Die Bedingungen in den if-Abfragen sollen immer den Inhalt von zwei Zeichenketten vergleichen, wobei die ersten drei Abfragen mit isEqual: oder isEqualToString: jeweils richtig formuliert sind und auch das erwartete Ergebnis liefern. Bei der Verwendung des Gleichheitsoperators erhalten Sie hingegen nur dann YES als Ergebnis, wenn sich beide Zeichenketten an der gleichen Speicheradresse befinden, also identisch sind.

Aus diesem Grund liefert der letzte Vergleich auch NO als Antwort. Der Inhalt der beiden Objekte, auf die theDynamicNumber und theNumber verweisen, ist zwar gleich, sie befinden sich indes an unterschiedlichen Stellen im Speicher. Da der Gleichheitsoperator allerdings Adressen und keine Inhalte vergleicht, ist das Ergebnis also NO.

»NSMutableString«

NSMutableString erweitert seine Superklasse NSString um Methoden zur Manipulation von Zeichenketten, mit denen sich Zeichen und Zeichenketten löschen, einfügen und ersetzen lassen. Das Erzeugen eines Objekts der Klasse NSMutableString erfolgt über die gleichen Methoden wie bei seiner Superklasse, z. B.:

NSMutableString *theMutableString = [NSMutableString 
stringWithFormat:@"Aller guten Dinge sind %i", 3];

Über die Methode appendFormat: können Sie eine Zeichenkette mit Platzhaltern anhängen:

[theMutableString appendFormat:@", %d oder %d", 4, 5];
// -> @"Alle guten Dinge sind 3, 4 oder 5"

Mit der Methode appendString: hängen Sie eine weitere, konstante Zeichenkette an eine veränderliche an. Um den Satz korrekt mit einem Punkt abzuschließen, können Sie die folgende Anweisung verwenden:

[theMutableString appendString:@"."];
// -> @"Aller guten Dinge sind 3, 4 oder 5."

Über die Methode insertString:atIndex: können Sie einen String an einer gewünschten Stelle einfügen:

[theMutableString insertString:@"hier " atIndex:18];
// -> @"Alle Dinge sind hier 3, 4 oder 5."

Und schließlich können Sie mit replaceCharactersInRange:withString: einen Bereich der Zeichenkette austauschen:

NSRange theRange = NSMakeRange(6, 5);
[theMutableString replaceCharactersInRange:theRange
withString:@"schlechten"];
// -> @"Alle schlechten Dinge sind hier 3, 4 oder 5.

Einschub: Formatstrings

Bei der Formatierung von NSLog-Ausgaben und Zeichenketten haben Sie bereits die Formatstrings kennengelernt. Darunter versteht man Zeichenketten mit Platzhaltern. Die App füllt sie zur Laufzeit mit den Parameterwerten, die auf den Formatstring folgen. Die Platzhalter geben dabei das gewünschte Format und den Typ des Wertes an. Ein Platzhalter eines Formatstrings beginnt stets mit einem Prozentzeichen. Um den Inhalt eines Objekts in die Konsole zu schreiben, ist der Platzhalter beispielsweise ein Prozentzeichen, auf das ein Klammeraffe folgt:

NSLog(@"Jetzt kommt der String %@ und ein Datum %@", 
theMutableString, [NSDate date]);

Formatstrings als Sicherheitslücke

In Kapitel 7, »Programmieren, aber sicher«, lernen Sie die Gefahren von Formatstrings näher kennen. Kurz gesagt liegt die Gefahr darin, dass ein Angreifer einem Platzhalter einen Wert zuweist, den der Programmierer nicht vorgesehen hat. Da in C die automatische Überprüfung von Pufferlängen nicht existiert, müssen Sie als Programmierer also selbst prüfen, ob die Daten unbedenklich sind.

Tabelle 2.3 gibt Ihnen einen Überblick über die wichtigsten Platzhalter. Verwenden können Sie diese überall, wo Formatstrings vorkommen, also nicht nur bei NSLog, sondern z. B. auch bei der Arbeit mit NSString. Eine vollständige Liste aller Formatstring-Platzhalter finden Sie in der Apple-Dokumentation.

Tabelle 2.3 Platzhalter für Formatstrings (Auswahl)

Platzhalter Parametertyp Bedeutung

%@

Objektreferenz

Ausgabe eines Objekts; genauer gesagt setzt die App hier das Ergebnis des Methodenaufrufs von description oder descriptionWithLocale: des Objekts ein.

%s

char *

C-Zeichenkette (z. B. "Hallo" [Anm.: Ohne Klammeraffe vor dem ersten Anführungszeichen] ), nicht zu verwechseln mit NSString-Objekten

%c

char

ein einzelnes 8-Bit-Zeichen wie 'x'

%C

unichar

ein 16-Bit-Unicode-Zeichen (UTF-16), wie es die Methode characterAtIndex: liefert

%hi

short, int16_t

vorzeichenloser Integer-Wert (16 Bit)

%hu

unsigned short, uint16_t

vorzeichenbehafteter Integer-Wert (16 Bit)

%i

int

vorzeichenbehafteter Integer-Wert (32 Bit)

%u

unsigned

vorzeichenloser Integer-Wert (32 Bit)

%qi

long long, int64_t

vorzeichenbehafteter Integer-Wert (64 Bit)

%qu

unsigned long long, uint64_t

vorzeichenloser Integer-Wert (64 Bit)

%p

Zeiger

Speicheradresse eines Objekts als Hexadezimalwert

%f

double, float, CGFloat

Gleitkommazahl

%e

Gleitkommazahl (Exponentialschreibweise)

Xcode warnt Sie übrigens, wenn ein Platzhalter und der entsprechende Wert nicht zueinander passen, und macht Ihnen außerdem einen Korrekturvorschlag.

»NSNumber«

Sie können bei der iOS-Programmierung ohne weiteres die aus C bekannten Datentypen wie int, float und double für Zahlenwerte verwenden. Dabei handelt es sich dann jedoch nicht um Objekte, sondern nur um einfache Werte ohne Methoden. Cocoa Touch stellt die Klassen NSNumber zur Verfügung, um Zahlen in Objekte verpacken zu können. Das ist beispielsweise dann notwendig, wenn Sie eine Zahl in ein Array oder ein Dictionary einfügen möchten, da diese Container ja nur Objekte verwalten können:

[NSArray arrayWithObjects: 
@"Dieser Satz kein Verb",
[NSNumber numberWithInt:26],
[NSNumber numberWithFloat:2.71828],
[NSNumber numberWithInt:theValue],
[NSNumber numberWithUnsignedInt:theValue + 3], nil]

Listing 2.119 Zahlen in ein Array einfügen

Das Verpacken eines einfachen Datentyps in ein Objekt nennt man auch Boxing. Ein NSNumber-Objekt merkt sich dabei auch, welchen Typ der enthaltene Wert ursprünglich hatte. Sie können ihn dennoch auch so auspacken, wie es Ihnen gerade passt:

NSNumber *theValue = [NSNumber numberWithInt:42];
NSLog(@"int = %d", [theValue intValue]);
NSLog(@"float = %f", [theValue floatValue]);
NSLog(@"BOOL = %@", [theValue boolValue] ? @"YES" : @"NO"];

Listing 2.120 Auspacken, es ist Weihnachten!

Sie können auch das Objekt direkt ausgeben. Dazu müssen Sie jedoch dann den Platzhalter %@ und nicht etwa %f oder %d verwenden. Die Anweisung NSLog(@"value = %i", theValue); gibt in der Regel eine andere Zahl aus, [Anm.: Das ist die Speicheradresse des Objekts.] als das Objekt enthält.

Mit der Version 4.1 des LLVM-Compilers hat Apple endlich die Fürbitten vieler Programmierer erhört und den Wirkungsbereich des @-Operators auf Zahlen, Arrays und Dictionarys ausgeweitet. Sie dürfen ihn nun also auch vor Zahlen schreiben, um damit NSNumber-Objekte zu erzeugen:

@3 // [NSNumber numberWithInt:3]
@3U // [NSNumber numberWithUnsignedInt:3U]
@3.33 // [NSNumber numberWithDouble:3.33]
@1.23F // [NSNumber numberWithFloat:1.23]

Listing 2.121 Beispiele für »NSNumber«-Literale

Variablen oder andere zahlwertige Ausdrücke schreiben Sie einfach in runde Klammern mit dem Operator davor, z. B. @(theValue), @(3 * theValue). Dieses Boxing funktioniert sogar auch mit C-Zeichenketten:

const char *theHome = getenv("HOME");
NSString *theHomePath = @(theHome);

Bei Arrays verwenden Sie den Klammeraffen und eckige Klammern, so dass sich das Array aus Listing 2.119 auch kurz als

@[@"Dieser Satz kein Verb", @26, @2.71828F, 
@(theValue), @(theValue + 3)]

mit einem NSArray-Literal schreiben lässt.

»NSData«

NSData ist die Klasse für alle binäre Daten, also Bytearrays. Dabei übernimmt in der Regel die Klasse auch die Verwaltung des Speichers zur Ablage der Daten, wenn Sie das Datenobjekt über die Methoden initWithBytes:length: oder dataWithBytes:length: erzeugen. Es kapselt also ein Bytearray und merkt sich auch dessen Länge, wofür Sie ja in der Regel in den C-Sprachen ansonsten zwei Variablen (für den Datenzeiger und die Länge) brauchen. Die Länge der enthaltenen Daten erhalten Sie über die Methode length und einen Zeiger auf die Daten über bytes.

NSData bietet analog zu der Klasse NSString die Möglichkeit, die Daten aus einer Datei mit dataWithContentsOfFile:options:error: oder über eine URL durch dataWithContentsOfURL:options:error: zu lesen. Die Dateivariante erlaubt auch Memory Mapping, wobei sie nicht den kompletten Dateiinhalt in den Speicher lädt, sondern nur die Teile, die das Programm gerade braucht:

NSError *theError = nil;
NSData *theData = [NSData dataWithContentsOfFile:thePath
options:NSDataReadingMappedIfSave error:&theError];

Listing 2.122 Datei mit Memory Mapping lesen

Dabei geschieht das Memory Mapping für Sie vollkommen transparent; Sie greifen also auf die Daten zu, und das Betriebssystem kümmert sich darum, dass sich die entsprechenden Speicherbereiche im Hauptspeicher des iPhones befinden.

Base64

In der Netzwerkprogrammierung und beim Umgang mit Kryptografie benötigt man häufig die sogenannte Base64-Kodierung. Damit lassen sich Binärdaten in ASCII-Zeichen kodieren. Base64 ist wohl einer der am häufigsten genutzten Standards für die Kodierung von Daten, und mit iOS 7 hat Apple endlich native Unterstützung für Base64 implementiert. NSData besitzt nun Methoden zum Kodieren und Dekodieren von Base64-Daten, und das bislang aufwendige Einbinden externer Bibliotheken für die Verarbeitung von Base64-Daten entfällt.

Im Kapitel 7, »Programmieren, aber sicher«, erfahren Sie mehr über die praktische Anwendung von Base64 über die betreffenden NSData-Methoden.


Rheinwerk Computing - Zum Seitenanfang

2.5.3CollectionsZur nächsten ÜberschriftZur vorigen Überschrift

Neben den elementaren Klassen, die in den vorangegangenen Abschnitten vorgestellt wurden, gehören auch die Collections zum Handwerkszeug. Collections sind Klassen, die beliebig viele Referenzen auf andere Objekte verwalten. Dazu gehören Arrays, Mengen und Dictionarys. Das Foundation-Framework besitzt nicht so viele Klassen für Container wie die Standardbibliotheken anderer Programmiersprachen (z. B. J2SE von Java oder die STL von C++), was jedoch kein Mangel ist, da das Foundation-Framework zum einen unterschiedliche Implementierungsvarianten für die einzelnen Klassen besitzt und zum anderen nicht reine Datenstrukturen aus dem Lehrbuch, sondern optimierte Mischformen implementiert. Als Beispiel sei hier NSMutableArray genannt, das nicht auf einem C-Array basiert, sondern die Daten in Blöcken verwaltet.

»NSArray«

Die Klasse NSArray und ihre Subklasse NSMutableArray haben Sie in diesem Kapitel bereits kennengelernt. Ein NSArray ist ein indizierter Container von Objekten. Es weist jedem enthaltenen Objekt genau einen fortlaufenden Indexwert zu. Die Indexwerte beginnen immer bei 0. Sie können über die Methode objectAtIndex: und den Index auf ein Objekt zugreifen und über die Methode count die Anzahl aller Objekte ermitteln. Seit Xcode 4.5 gibt es auch einen Subskriptionsoperator für den Elementzugriff, womit sich der Aufruf [theArray objectAtIndex:5] wesentlich kürzer als theArray[5] schreiben lässt.

Tipp

Über den Menüpunkt EditRefactorConvert to Modern Objective-C Syntax... können Sie übrigens Xcode veranlassen, im Code NSNumber-Objekte durch NSNumber-Literale und Array-Zugriffe durch Subskriptionsoperatoren zu ersetzen.

Über die Methode componentsJoinedByString: lassen sich aus Arrays leicht Zeichenketten für die Ausgabe erzeugen. Denken Sie beispielsweise an die Liste der Gehälter aus Abschnitt 2.2.10, »Key-Value-Coding«, die Sie folgendermaßen als kommaseparierte Liste ausgeben:

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

NSLog(@"salaries = %@", [theSalaries
componentsJoinedByString:@", "];

Listing 2.123 Ausgabe einer kommaseparierten Liste

Auch die sortierte Ausgabe dieser Werte ist mit der Methode sortedArrayUsingSelector: kein Problem. Dabei muss der Selektor auf eine Nachricht mit einem Parameter verweisen, die zwei Objekte der gleichen Klasse miteinander vergleicht. Dafür können Sie bei Zeichenketten oder Zahlen die Methode compare: verwenden.

NSArray *theSalaries = [theManager 
valueForKeyPath:@"department.employees.salaray"];
NSArray *theSortedSalaries = [theSalaries
sortedArrayUsingSelector:@selector(compare:)];

NSLog(@"salaries = %@", [theSortedSalaries
componentsJoinedByString:@", "];

Listing 2.124 Ausgabe einer kommaseparierten, sortierten Liste

»NSDictionary«

Das Verwalten von Objekten anhand eines Index ist nicht immer möglich. Insbesondere ist das manuelle Arbeiten mit indizierten Arrays nicht hilfreich, wenn Sie ein bestimmtes Objekt suchen. Entweder müssen Sie sich die Position des Objekts merken, oder Sie müssen alle Objekte durchlaufen und für jedes Objekt prüfen, ob es das gesuchte ist. Hierfür eignen sich in der Regel Dictionarys besser, die jedes Objekt zu einem Schlüssel ablegen, der in vielen Fällen eine Zeichenkette ist.

Die Klasse NSDictionary stellt diese Ablagemöglichkeit für Objekte bereit. Bei der Erzeugung weisen Sie jedem Objekt einen eindeutigen Schlüssel zu, dessen Klasse allerdings die Methode copy implementieren muss.

Konstante Dictionarys erzeugen Sie in vielen Fällen über die Methode dictionaryWithObjectsAndKeys:, der Sie abwechselnd die Objekte und die dazugehörenden Schlüssel übergeben. Diese Liste müssen Sie übrigens immer (wie auch bei der Methode arrayWithObjects: der Klasse NSArray) mit dem Wert nil abschließen, damit die Methode erkennen kann, wo sie endet. Das klingt seltsam, da doch das Ende im Programmcode offensichtlich zu erkennen ist. Die Methode bekommt im Aufruf jedoch keine Liste mit definierter Länge, sondern binäre Daten ohne Längenangabe übergeben. Ein Dictionary können Sie beispielsweise so anlegen:

NSDictionary *theDictionary = [NSDictionary 
dictionaryWithObjectsAndKeys:@"red", @"color",
[[Droid alloc] initWithID:3], @"droid",
@"number", @3, nil];

Im Gegensatz zu vielen anderen Sprachen geben Sie hier immer zuerst das Objekt und dann seinen Schlüssel an, was ja auch konsistent mit der KVC-Methode setValue:forKey: ist. In dem Beispiel ist anscheinend das letzte Wert-Schlüssel-Paar zu einem Schlüssel-Wert-Paar vertauscht worden; zumindest legt es den Wert number unter dem Schlüssel 3 ab und nicht umgekehrt.

Auch für Dictionarys unterstützt der LLVM-Compiler inzwischen ein Literal, mit dem sich die Konstruktion kürzer ausdrücken lässt. Hierbei geben Sie allerdings immer zuerst den Schlüssel und danach das Objekt an. Das Dictionary aus dem Beispiel können Sie damit so schreiben:

NSDictionary *theDictionary = @{ @"color" : @"red", 
@"droid" : [[Droid alloc] initWithID:3],
@3 : @"number" };

Der Zugriff auf ein Element im Dictionary erfolgt entweder über Key-Value-Coding, die Methode objectForKey: oder den Subskriptionsoperator. Dabei stellt der Zugriff über die KVC-Methode valueForKey: einen Fehler dar, wenn das Dictionary den Schlüssel nicht enthält:

[theDictionary valueForKey:@"color"] // OK -> @"red"
[theDictionary objectForKey:@"color"] // OK -> @"red"
theDictionary[@"color"] // OK -> @"red"
[theDictionary valueForKey:@"city"] // Fehler
[theDictionary objectForKey:@"city"] // OK -> nil
theDictionary[@"color"] // OK -> nil
theDictionary[@"number"] // OK -> nil
theDictionary[@3]; // OK -> @"number"
theDictionary[@"3"]; // OK -> nil

Der letzte Ausdruck liefert nil, weil der Schlüssel im Dictionary ja ein NSNumber-Objekt mit dem Wert 3 und keine Zeichenkette mit dem Zeichen »3« ist. Hier findet also keine automatische Typumwandlung wie in vielen Skriptsprachen, z. B. in PHP oder JavaScript, statt.

Sie können Dictionarys auch in einer Fast Enumeration verwenden, die dann alle Schlüssel durchläuft. Mit

for(id theKey in theDictionary) {
id theValue = theDictionary[theKey];
...
}

durchlaufen Sie beispielsweise alle Schlüssel-Wert-Paare eines Dictionarys.

»NSSet«

Eine Menge ist ein Container, in dem jeder Wert höchstens einmal vorkommen darf, wofür das Foundation-Framework die Klasse NSSet bereitstellt. Eine Menge legt die Elemente nach einem festen Algorithmus ab, der sich aber nicht aus der Reihenfolge des Einfügens herleitet, weswegen man eine Menge auch als ungeordnet bezeichnet. Dadurch ist es auch nicht möglich, auf ein bestimmtes Element – wie bei Arrays über den Index oder bei Dictionarys über den Schlüssel – zuzugreifen. Sie können stattdessen alle Elemente über eine Fast Enumeration durchlaufen oder über anyObject ein willkürliches Element [Anm.: »Willkürlich« bedeutet hier nicht »zufällig«, da die Menge das Element nach einem festgelegten Verfahren bestimmt. Bei einer konstanten Menge bekommen Sie übrigens immer das gleiche Objekt.] auslesen.

Der Unterschied einer Menge gegenüber einem Array ist zum einen die garantierte Einmaligkeit der enthaltenen Elemente, und zum anderen können Sie damit schneller testen, ob ein bestimmter Wert enthalten ist. Die Einmaligkeit der Elemente lässt sich beispielsweise ausnutzen, um die unterschiedlichen Gehälter der Mitarbeiter aus Abschnitt 2.2.10 zu bestimmen:

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

NSLog(@"salaries = %@", [[theUniqueSalaries allObjects]
componentsJoinedByString:@", "];

Listing 2.125 Bestimmung der unterschiedlichen Werte eines Arrays

Dabei liefert die Methode allObjects ein Array, das alle Elemente der Menge enthält.

Die Methode containsObject: prüft, ob sich das angegebene Objekt in der Menge befindet. Da diese Überprüfung bei Mengen im Gegensatz zu Arrays besonders schnell ist, eignen sich veränderliche Mengen besonders gut als Strichliste, um sich verarbeitete Werte zu merken. Beispielsweise könnte das Modell des Beispielprojekts eine Methode addDroid: bereitstellen, die es dem Controller erlaubt, Droiden einzufügen. Die Methode soll jedoch keine Droiden einfügen, deren Kennung bereits bekannt ist. Das können Sie mit einer weiteren privaten Property droidIDs vom Typ NSMutableSet und folgender Implementierung erreichen:

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

-
(BOOL)addDroid:(Droid *)inDroid {
if([self.droidIDs containsObject:inDroid.droidID]) {
return NO;
}
else {
[self.droidIDs addObject:inDroid.droidID];
[self willChangeValueForKey:@"countOfObjects"];
[self.objects addObject:inDroid];
[self didChangeValueForKey:@"countOfObjects"];
return YES;
}
}


Listing 2.126 Hier hinein kommen nur Droiden mit eigener Kennung.

Natürlich sollten Sie jetzt die Methode updateDroids: so anpassen, dass sie auch die Methode addDroid: verwendet, anstatt den neuen Droiden direkt in das Array objects einzufügen.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< 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


  Zum Katalog
Zum Katalog: Apps programmieren für iPhone und iPad






Neuauflage: Apps programmieren für iPhone und iPad
Jetzt 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


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo