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.3Speicherverwaltung und PropertysZur nächsten Überschrift

Bevor Sie im nächsten Kapitel mit der Programmierung einer umfangreicheren iOS-App anfangen, steht noch die Speicherverwaltung auf dem Plan. In den bislang vorgestellten Beispielen haben wir das Thema immer umgangen, und es schimmerte nur an einigen Stellen etwas durch.

Glücklicherweise hat sich die richtige Verwaltung des Speichers seit iOS 5 mit der Einführung des Automatic Reference Countings (ARC) wesentlich vereinfacht. Dabei sorgt in fast allen Fällen der Compiler dafür, dass mit dem Speicher alles rundläuft.

Der Speicher eines Programms teilt sich in drei Bereiche auf. Da ist zunächst der Programmspeicher, der den ausführbaren Code des Programms sowie alle Konstanten enthält. Diesen Bereich können Sie nur auslesen, aber nicht verändern, und er enthält – vereinfacht ausgedrückt – die Daten aus der Programmdatei von der Festplatte. Wenn Sie beispielsweise eine konstante Zeichenkette wie @"Ich heiße Konstante." definieren, dann legt der Compiler die Zeichen und gegebenenfalls notwendigen Verwaltungsinformationen dafür in diesen Speicherbereich, und Sie brauchen sich um dessen Verwaltung nicht zu kümmern.


Rheinwerk Computing - Zum Seitenanfang

2.3.1Stack und HeapZur nächsten ÜberschriftZur vorigen Überschrift

Immer wenn Sie eine Methode oder Funktion aufrufen oder eine lokale Variable verwenden, benötigt das Programm dafür Speicher, den es im Stack verwaltet. Jeder Funktions- und Methodenaufruf reserviert dabei implizit einen Teilbereich des Stacks, auch Stackframe genannt, und gibt ihn am Ende der Ausführung wieder frei. In diesem Teilbereich liegen die lokalen Daten, die Rücksprungadresse und gegebenenfalls weitere Informationen.

Der Name dieses Speicherbereichs hängt übrigens mit seiner Verwaltung zusammen: Wie bei einem Stapel können Sie entweder neue Daten oben darauflegen, den obersten Stackframe verwenden oder ihn entfernen. Das Betriebssystem stapelt also Stackframe über Stackframe. Das Hinzufügen eines neuen Stackframes bezeichnet man auch als push und das Entfernen als pop.

Da die Größe des Stacks begrenzt ist, kann es zu einem Stapelüberlauf beziehungsweise Stack-Overflow kommen, wodurch das Programm abstürzt. Diese Ausnahmesituation tritt beispielsweise bei einer Endlosrekursion auf. Bei diesem Programmierfehler ruft sich eine Methode oder Funktion immer wieder selbst auf. Jeder dieser Aufrufe verbraucht jedoch Speicher auf dem Stack. Das geht so lange, bis dieser Speicherbereich voll ist, und dann stürzt das Programm ab.

Da Sie jedoch den Stapelspeicher nicht explizit anfordern oder freigeben können, brauchen Sie sich auch nicht um seine Verwaltung zu kümmern.

Der Heap ist der Bereich des Hauptspeichers, von dem das Programm beliebige Speicherblöcke anfordern kann, und zwar so lange, bis kein freier Speicher mehr zur Verfügung steht. Die App legt hier beispielsweise alle Objekte ab, deren Speicher sie über die Klassenmethode alloc reserviert. Anschließend existiert das neue Objekt als ein Speicherbereich im Heap, und die Information, an welcher Stelle sich das Objekt befindet, enthält die Objektreferenz, der Sie das Ergebnis der Objekterzeugung zuweisen. Bei dem Ausdruck

NSMutableArray *objects = [[NSMutableArray alloc] init];

zeigt also der Zeiger objects auf ein Objekt der Klasse NSMutableArray.

Zeigervariablen

Im Heap gibt es keine besonderen, für Sie sichtbaren Verwaltungsstrukturen, und der Zugriff auf die Daten und Objekte erfolgt immer über ihre jeweilige Speicheradresse. Das sind die in C und C++ berühmt-berüchtigten Zeiger, die Sie im Quelltext am Sternchen erkennen. Eine Zeigervariable enthält also nicht die eigentlichen Daten, sondern verweist nur darauf. Die folgenden Anweisungen sollen das verdeutlichen:

int theValue = 2;
int *thePointer = &theValue;
NSLog(@"1: value = %d, pointer = %d", theValue, *thePointer);
theValue = 4;
NSLog(@"2: value = %d, pointer = %d", theValue, *thePointer);
*thePointer = 8;
NSLog(@"3: value = %d, pointer = %d", theValue, *thePointer);

Die Zeigervariable thePointer in dem Listing zeigt auf die Speicherstelle, an der sich der Wert der Variablen theValue befindet. Diese Zuordnung geschieht in der zweiten Zeile durch den Operator &, der die Speicheradresse der Variablen theValue liefert. Das Gegenstück dazu ist der Operator *, in der Form, wie er in den Log-Anweisungen steht. Er dereferenziert den Zeiger und liefert somit den Wert aus der Speicherstelle, auf die der Zeiger verweist.

Da der Zeiger also nicht selbst eine Zahl speichert, sondern nur auf die der Variablen theValue verweist, geben alle drei Log-Anweisungen jeweils den gleichen Wert für theValue und *thePointer aus. Die Ausgabe lautet also:

1: value = 2, pointer = 2
2: value = 4, pointer = 4
3: value = 8, pointer = 8

Die Variablen auf Objective-C-Objekte sind zwar immer Zeiger, allerdings brauchen Sie sich dabei nicht um die Operatoren & und * oder Ähnliches zu kümmern.

Es gibt jedoch noch einen besonderen Wert für Zeiger, und das ist die Speicheradresse 0, die Objective-C über die Konstante nil und C über NULL darstellt, wobei NULL auch in Objective-C bei C-Zeigern Verwendung findet. Durch den Wert nil zeigen Sie an, dass eine Objektreferenz auf kein Objekt verweist.

Abbildung 2.37 zeigt die Speicherbelegung eines Objekts der Klasse Model. Dabei liegen sowohl das Modellobjekt als auch seine Property-Werte bis auf den Namen im Heap. Als Name verwendet das Beispielprojekt ja die Konstante @"LoremIpsum", weshalb dieses Objekt nicht im Heap, sondern im Speicherbereich des Programmcodes liegt. An der Darstellung sehen Sie auch, dass selbst bei kleinen Klassen mit wenigen Propertys oder Attributen schnell große Objektgraphen mit vielen Knoten, also Objekten, entstehen, wobei für jedes Objekt mindestens eine Referenz notwendig ist, um darauf zugreifen zu können. Dabei ist die Darstellung noch nicht einmal vollständig, da ja beispielsweise jeder Droide wieder auf eine Zeichenkette mit seiner Kennung verweist.

In einer idealen Welt ist Speicherplatz unbegrenzt, und eine App kann so viel Speicher vom Heap anfordern, wie sie will. In der Praxis sieht das allerdings anders aus. Gerade auf einem mobilen Gerät wie einem iPhone ist der Speicher knapp, und deswegen muss man mit dem zur Verfügung stehenden Speicher gut haushalten. Das bedeutet, dass Sie nicht mehr benötigten Speicher freigeben müssen.

Dabei können zwei mögliche Fehler entstehen: Speicherlecks (auch Leaks genannt) oder Dangling Pointer. Ein Speicherleck entsteht, wenn Sie ein nicht mehr verwendetes Objekt nicht freigegeben, was dazu führt, dass es unnötig Speicherplatz belegt. Zu viele Lecks belegen mit der Zeit den gesamten verfügbaren Hauptspeicher und lassen das Programm abstürzen. Das ist wie in einem All-inclusive-Hotel auf den Balearen: Auf allen Sonnenliegen liegt schon vor dem Frühstück jeweils ein Handtuch, und für Sie ist keine Liege mehr frei. Na ja, am Strand ist es sowieso schöner.

Ein Dangling Pointer hingegen ist das Gegenteil: ein Verweis auf ein freigegebenes Objekt. Findet innerhalb einer App ein Zugriff auf ein bereits freigegebenes Objekt statt, führt das in der Regel zu einem unerwarteten Programmverhalten oder zu einem Absturz.

Abbildung

Abbildung 2.37 Ein Modellobjekt des Beispielprogramms im Speicher


Rheinwerk Computing - Zum Seitenanfang

2.3.2Starke und schwache ReferenzenZur nächsten ÜberschriftZur vorigen Überschrift

Die Verwaltung des Heapspeichers ist also schwierig, weswegen Apple Ihnen hierfür Mechanismen bereitstellt, die Ihnen dabei helfen. Die Grundregel für diese automatische Verwaltung für ein einzelnes Objekt lautet: Wenn es kein anderes Objekt mehr braucht, dann kann es die Laufzeitumgebung zerstören.

Objekte zeigen über Referenzen an, dass Sie ein anderes Objekt benötigen, und es gibt zwei Arten von Referenzen:

  1. Starke Referenzen halten ihr referenziertes Objekt; also die Laufzeitumgebung kann ein Objekt erst zerstören, wenn keine starke Referenz mehr darauf verweist.
  2. Im Gegensatz dazu halten schwache Referenzen nicht das referenzierte Objekt. Die Laufzeitumgebung kann das Objekt einer schwachen Referenz also jederzeit zerstören, sofern sie sich dabei an die Bedingung für starke Referenzen des ersten Punktes hält.

Das automatische Referenzzählen erkennt dabei, wann keine starken Referenzen mehr auf ein Objekt verweisen, und es entfernt dann dieses Objekt aus dem Speicher. Damit Ihre App weder Lecks noch Dangling Pointer erzeugt, müssen Sie also bei der Programmierung entscheiden, wann Sie starke oder schwache Referenzen einsetzen. Der falsche Einsatz einer starken anstatt einer schwachen Referenz führt im schlimmsten Fall zu einem Speicherleck. Im umgekehrten Fall erhalten Sie in der Regel eine Referenz auf nil oder schlimmstenfalls einen Dangling Pointer. Aber keine Sorge, das hört sich komplizierter an, als es ist, da Sie in vielen Fällen starke Referenzen verwenden können, ohne Speicherlecks zu erzeugen.

Lokale Variablen sind starke Referenzen, sofern Sie sie nicht explizit als schwach kennzeichnen. Sie halten ein referenziertes Objekt bis zum Ende seines Gültigkeitsbereiches oder bis das Programm ihm ein anderes Objekt zuweist.

- (void)strongDroids {
Droid *theFirstDroid = [[Droid alloc] initWithID:1];
Droid *theSecondDroid = [[Droid alloc] initWithID:2];

if(YES) {
Droid *theInnerDroid = [[Droid alloc] initWithID:3];

theSecondDroid = theFirstDroid;
}
}

Listing 2.80 Droiden und starke Referenzen

Die Methode in Listing 2.80 erzeugt drei Droiden, die sie über lokale Variablen referenziert. Die Variable theInnerDroid hält den Droiden mit der Kennung 3. Da die Methode dieser Variablen keinen anderen Wert zuweist, referenziert sie diesen Droiden bis zum Ende des if-Blocks, und die Laufzeitumgebung kann ihn erst dann zerstören. Analog verhält es sich mit dem Droiden mit der Kennung 1, der allerdings bis zum Ende der Methode existiert, da ja der Gültigkeitsbereich der Variablen theFirstDroid der gesamte Methodenrumpf ist. Vor seiner Zerstörung kann ihn auch nicht die Referenz theSecondDroid bewahren, da sie zwar auch den ersten Droiden hält, jedoch auch ihr Gültigkeitsbereich der Methodenrumpf ist.

Den Droiden mit der Kennung 2 ereilt hingegen früher sein Schicksal. Durch die Zuweisung am Ende des if-Blocks verliert er die einzige haltende Referenz, und die Laufzeitumgebung verwandelt ihn augenblicklich in Altmetall, indem sie ihn aus dem Speicher löscht.

Sie können über die Schlüsselwörter __weak und __unsafe_unretained lokale Variablen als schwache Referenzen festlegen.

Droid *theFirstDroid = [[Droid alloc] initWithID:1];
__weak Droid *theWeakDroid = theFirstDroid;
__weak Droid *theSecondDroid = [[Droid alloc] initWithID:2];
theFirstDroid = [[Droid alloc] initWithID:3];

Listing 2.81 Droiden mit starken und schwachen Referenzen

Die Variable theWeakDroid verweist auf den Droiden mit der Kennung 1, bis der Code der Variablen theFirstDroid einen neuen Droiden in der letzten Anweisung von Listing 2.81 zuweist. Da theWeakDroid eine schwache Referenz ist, hält sie den Droiden nicht, und die Laufzeitumgebung zerstört ihn. Dieser Fall tritt auch bei der Variablen theSecondDroid ein: Da sie den Droiden mit der Kennung 2 nicht hält, zerstört ihn die Laufzeitumgebung sofort nach seiner Erzeugung, und die Variable theSecondDroid zeigt immer auf nil.

Schwache Referenzen

Wenn die Laufzeitumgebung ein Objekt löscht, auf das eine schwache Referenz verweist, setzt sie die Referenz auch auf nil, sofern sie den Modifizierer __weak hat. Haben Sie in der Deklaration hingegen __unsafe_unretained verwendet, verändert die Laufzeitumgebung hingegen die Referenz nicht. Sie erhalten also eine Referenz auf ein ungültiges Objekt.

Im Folgenden verwenden wir zwar diese (und andere) Modifizierer für verschiedene Programmbeispiele, um mögliche Implementierungsfälle zu illustrieren. In der Praxis brauchen Sie diese Modifizierer jedoch sehr selten.

Falls eine Methode einen Wert zurückliefert, wartet die Laufzeitumgebung mit der Freigabe der starken Referenz auf den Rückgabewert, bis das Programm den Wert der Variablen des Aufrufers zugewiesen hat. Die Methode pop, die das oberste Element eines Stapels liefert, könnte beispielsweise so aussehen:

- (id)pop {
id theItem = [self.items lastObject];

[self.items removeLastObject];
return theItem;
}

Listing 2.82 Löschen und Rückgabe des obersten Stapelelementes

Dabei liefert die Methode lastObject, wie der Name schon vermuten lässt, das letzte Objekt des Arrays items, und falls das Array leer ist, den Wert nil. Analog dazu verhält sich die Methode firstObject mit dem ersten Objekt in dem Array. Wenn nun das Programm diese Methode über

id theTopItem = [theStack pop];

aufruft, löscht die Laufzeitumgebung die starke Referenz theItem aus Listing 2.82 erst, nachdem sie die starke Referenz von theTopItem auf das gleiche Objekt hergestellt hat. Der Rückgabewert geht also hier nicht verloren.


Rheinwerk Computing - Zum Seitenanfang

2.3.3AutoreleasepoolsZur nächsten ÜberschriftZur vorigen Überschrift

Falls jedoch der Aufrufer eine schwache Referenz auf das zurückgegebene Objekt verwendet, gibt die Laufzeitumgebung das Objekt sofort frei:

__weak id theTopItem = [theStack pop];

In dieser Situation können Sie über die Verwendung des Autoreleasepools ein sofortiges Löschen des Objekts verhindern, wozu Sie die Methode pop anpassen müssen:

- (id)pop {
__autoreleasing id theItem = [self.items lastObject];

[self.items removeLastObject];
return theItem;
}

Listing 2.83 Verwendung des Autoreleasepools

Die Funktionsweise des Autoreleasepools ist relativ einfach. Er ist eine Sammlung von starken Referenzen auf beliebige Objekte. Sie können eine Referenz auf ein Objekt zu dieser Sammlung hinzufügen, indem Sie den Modifizierer __autoreleasing wie in Listing 2.83 verwenden. Dabei kann es passieren, dass der Autoreleasepool mehrere Referenzen auf ein Objekt enthält, was jedoch unproblematisch ist.

Es gibt noch weitere Möglichkeiten, Objekte in den Autoreleasepool einzufügen. Beispielsweise legen Convenience-Konstruktoren, wie etwa stringWithFormat: der Klasse NSString, eine Referenz auf das neu erzeugte Objekt in den Autoreleasepool.

Irgendwann zerstört das Programm den Autoreleasepool, der dann die enthaltenen starken Referenzen freigibt. Die Zerstörung des Autoreleasepools ist allerdings nicht zeitgesteuert, sondern geschieht immer dann, wenn das Programm in die Runloop zurückkehrt. Dabei ist die Runloop der Modus, in dem das Programm auf neue Ereignisse wartet. Das können Nutzereingaben, Anrufe, neue Daten aus Internetverbindungen und vieles mehr sein.

Sie können über das Schlüsselwort @autoreleasepool auch eigene Autoreleasepools anlegen. Das ist beispielsweise in Schleifen sinnvoll, die sehr viele Referenzen im Autoreleasepool anlegen oder sehr große Datenmengen über den Autoreleasepool referenzieren. Listing 2.84 zeigt ein Code-Fragment, das 1.024 (= 64 × 16) Datenobjekte über den Autoreleasepool referenziert.

for(int i = 0; i < 64; ++i) {
for(int j = 0; j < 16; ++j) {
NSString *theName =
[NSString stringWithFormat@"%d-%d", i, j];
NSString *theFile = [thePath
stringByAppendingPathComponent:theName];
NSData *theData = [NSData dataWithContentsOfFile:
theFile];
...
}
}

Listing 2.84 Große Datenmengen im Autoreleasepool

Bei kleinen Datenobjekten (höchstens einige Kilobyte pro Datenobjekt) sollte das auch kein Problem sein. Wenn jedoch jedes Datenobjekt beispielsweise 1 MB groß ist, referenziert der Pool über 1 GB Daten, was unweigerlich zu einem Absturz führt. Das passiert sogar dann, wenn die App die Daten außerhalb der inneren Schleife nicht mehr braucht.

Sie können diese Situation durch einen eigenen Autoreleasepool um die innere Schleife entschärfen (siehe Listing 2.85). Durch diese Änderung referenziert der neue Autoreleasepool die Datenobjekte und gibt sie am Ende seines Blocks frei. Dieser Autoreleasepool referenziert nur maximal ungefähr 16 MB, bei 1 MB großen Datenobjekten, was in der Regel jedoch zu keinen Problemen führen sollte.

for(int i = 0; i < 64; ++i) {
@autoreleasepool {
for(int j = 0; j < 16; ++j) {
NSString *theName =
[NSString stringWithFormat:@"%d-%d", i, j];
NSString *theFile = [thePath
stringByAppendingPathComponent:theName];
NSData *theData = [NSData dataWithContentsOfFile:
theFile];
...
}
}
}

Listing 2.85 Speichersparen durch eigenen Autoreleasepool


Rheinwerk Computing - Zum Seitenanfang

2.3.4Propertys und AccessorenZur vorigen Überschrift

Bei lokalen Variablen können Sie in der Regel starke Referenzen verwenden und brauchen keine Modifizierer anzugeben. Die Laufzeitumgebung löscht ja schließlich die starken Referenzen lokaler Variablen spätestens am Ende des Gültigkeitsbereiches. Bei Referenzen zwischen Objekten ist der Referenztyp hingegen viel wichtiger. Hier können schwache Referenzen notwendig sein, um durch Referenzzyklen keine Speicherlecks zu erzeugen.

Referenzen zwischen Objekten stellen Sie in der Regel über Propertys her, und bereits bei der ersten Verwendung von Propertys ist Ihnen sicherlich aufgefallen, dass Sie die Property-Deklaration über Parameter beeinflussen können. Da diese Parameter eng mit der Speicherverwaltung zusammenhängen, sind wir allerdings noch nicht näher darauf eingegangen. Tabelle 2.1 listet alle möglichen Parameter nach Gruppen unterteilt auf.

Tabelle 2.1 Parameter für Property-Deklarationen

Name Gruppe Beschreibung

setter=Name

./.

Legt den Namen der Methode zum Setzen des Wertes fest. Der Name hat hingegen keine Auswirkung auf den Property-Zugriff mit der Punktnotation.

getter=Name

./.

Legt den Namen der Methode zum Lesen des Wertes fest. Der Name hat keine Auswirkung auf den Property-Zugriff mit der Punktnotation.

readwrite

Schreibbarkeit

Die Property ist les- und schreibbar. Das ist der Standard.

readonly

Die Property ist nur lesbar.

assign

Speicherverwaltung

Der Property hält nicht das Objekt; es muss also noch mindestens eine haltende Referenz geben. Andernfalls entsteht ein Dangling Pointer.

weak

Der Property hält das Objekt nicht. Im Gegensatz zu assign setzt die Laufzeitumgebung bei der Freigabe der letzten haltenden Referenz den Wert auf nil. Hier kann also kein Dangling Pointer entstehen.

retain, strong

Der Property hält das Objekt, und die Property-Implementierung sorgt für seine korrekte Freigabe.

copy

Die Property erzeugt (über die Methode copy des Objekts) und hält eine Kopie des zugewiesenen Objekts. Auch hier sorgt die Implementierung für eine korrekte Freigabe dieser Kopie.

atomic

Atomarität

Die Property ist atomar, d. h., der Compiler sichert Änderungen des Property-Wertes bei mehreren Threads gegen bestimmte Fehler ab.

nonatomic

Die Property ist nicht atomar. Atomare Propertys sind gegen gleichzeitige Änderungen durch unterschiedliche Threads abgesichert.

Sie können diese Parameter in einer Property-Deklaration durch Kommas getrennt beliebig kombinieren. Dabei dürfen allerdings die verschiedenen Parameter nicht zur gleichen Gruppe gehören. Wenn die Property auf ein anderes Objekt verweist, müssen Sie immer einen Wert aus der Gruppe »Speicherverwaltung« angeben. Andernfalls erhalten Sie eine Compiler-Warnung. Bei einfachen Datentypen (beispielsweise BOOL, int, double) müssen und sollten Sie hingegen keinen Speicherverwaltungsparameter angeben.

Wenn Sie eine Property in der Klassendeklaration als readonly gekennzeichnet haben, können Sie diese Einstellung in der anonymen Kategorie der Klasse mit readwrite überschreiben. Dabei müssen indes alle anderen Parameter gleich bleiben. Dadurch können Sie im Beispielprojekt beispielsweise erreichen, dass der Setter für die Property name eines Modells außerhalb von deren Implementierung nicht mehr aufrufbar ist. Dazu ändern Sie die Deklaration in der Headerdatei in

@property(copy, readonly) NSString *name;

und in die anonyme Kategorie fügen Sie die hervorgehobene Zeile aus Listing 2.86 ein:

@interface Model()

@property
(copy, readwrite) NSString *name;

@end

Listing 2.86 Redeklaration einer Read-Only-Property als schreibbar

Dadurch kann die Klasse Model den Wert der Property beliebig ändern, und alle anderen Klassen haben jedoch keinen schreibenden Zugriff mehr.

Die Parameter der Gruppe »Speicherverwaltung« unterscheiden sich darin, ob sie die Property als starke oder schwache Referenzen anlegen. Eine starke Referenz hat einen der Parameter retain, strong oder copy, wobei retain und strong Synonyme sind. Wenn Sie ein Objekt einer starken Referenz zuweisen, kann es die Laufzeitumgebung nicht aus dem Speicher entfernen. Im Gegensatz dazu halten schwache Referenzen, also Propertys mit den Parametern assign und weak, das referenzierte Objekt nicht; es kann somit passieren, dass das referenzierte Objekt nicht mehr existiert, ohne dass die App der Property neuen Wert zugewiesen hat.

Hier sind einige Property-Deklarationen angegeben, wie sie in einer Klasse Person stehen könnten:

@property NSInteger personId;
@property(nonatomic, getter=isFemale) BOOL female;
@property(copy) NSString *firstName;
@property(copy) NSString *lastName;
@property(strong) NSURL *imageURL;
@property(weak) Person *spouse;

Listing 2.87 Beispiele für Property-Deklarationen

Bei der Property personId handelt es sich nicht um eine Referenz auf ein anderes Objekt. Deswegen enthält die Deklaration auch keinen Parameter aus der Gruppe »Speicherverwaltung«.

Die Property female ist nicht-atomar, und ihre Methode zum Lesen hat den Namen isFemale anstatt female. In Cocoa verwendet man bei booleschen Attributen häufig isAttribute anstelle von attribute für den Methodennamen. Sie greifen also auf diese Methode mit der Bracket-Syntax (z. B. [thePerson isFemale]) zu. Für die Punktnotation müssen Sie hingegen den Namen der Property verwenden, also thePerson.female.

Die Propertys für den Vor- und Nachnamen verwenden copy. Das ist sinnvoll, weil es eine Subklasse NSMutableString von NSString gibt, die Sie bereits kennengelernt haben. Zeichenketten dieser Klasse können Sie im Gegensatz zu Objekten der Klasse NSString verändern. Durch das copy erzeugt die Zuweisung eine unveränderliche Kopie für den Attributwert. In diesem Fall hat also der Property-Wert immer die Klasse NSString.

Deklarieren Sie diese Propertys hingegen mit strong, so kann die App den Inhalt dieser Propertys verändern, ohne die Methoden zum Setzen aufzurufen.

theName = [NSMutableString stringWithString:@"Fritz"];
thePerson.firstName = theName;
[theName setString:@"Frank"];

Listing 2.88 Änderung des Property-Wertes durch veränderliche Objekte

Die erste Zeile setzt den Vornamen der Person auf den Namen »Fritz«. Wenn Sie die Property firstName als strong deklarieren, dann verweist das Attribut auf das gleiche Objekt wie die Variable theName. Der Methodenaufruf in der letzten Zeile verändert den Wert dieses Objekts in »Frank«, und dadurch ändert sich auch der Inhalt der Property firstName. Das ist in der Regel jedoch nicht erwünscht, da beispielsweise Key-Value-Observing solche Änderungen nicht mitbekommt.

Die Verwendung von copy verhindert dieses unerwünschte Verhalten, weil der Setter eine Kopie der Zeichenkette erzeugt und als Attributwert verwendet. Auf diese Kopie verweist nun die Property firstName. Dadurch bleibt sie von der Änderung in der letzten Zeile unberührt. Die Kopie ist auch kein NSMutableString, sondern immer ein NSString und somit vor Veränderungen geschützt.

Bei NSURL gibt es die Problematik mit veränderlichen Objekten nicht, so dass Sie hier getrost strong verwenden können. Sie können hier auch copy verwenden. Dadurch entsteht keinerlei Overhead, da die nicht-veränderlichen Klassen (z. B. NSURL, NSString oder NSDictionary) beim Kopieren einfach das gleiche Objekt als Kopie verwenden. Sie kopieren hier also in Wirklichkeit nicht. Stattdessen gibt die Methode copyWithZone:, die Kopien des Objekts erzeugt und die auch copy verwendet, einfach nur self zurück. [Anm.: Das Protokoll NSCopying deklariert diese beiden Methoden.]

Für die Ehegatten-Property spouse sollten Sie hingegen den Speicherverwaltungstyp assign oder weak verwenden. Da die Ehegatten sich gegenseitig referenzieren, entsteht hier eine zyklische Referenz. Dafür dürfen Sie keinesfalls starke Referenzen verwenden, da ansonsten Speicherlecks entstehen. Wenn beispielsweise Fritz mit Erna verheiratet ist, dann verweist Fritz auf Erna und Erna auf Fritz. Wenn nun diese Verweise den Typ strong hätten, dann hielte Fritz Erna und Erna Fritz im Speicher halten. Die Freigabe der letzten Referenz auf Fritz oder Erna würde diese Objekte nicht löschen, da sie sich ja über spouse immer noch gegenseitig halten. Ist das nicht romantisch?

Zwischen weak und assign besteht noch ein fundamentaler Unterschied, wenn die Laufzeitumgebung das referenzierte Objekt aus dem Speicher löscht. Bei weak setzt sie die Referenz auf nil, während sie bei assign die Referenz nicht ändert; assign entspricht also __unsafe_unretained, das Sie ja bereits kennengelernt haben. Dadurch können im Programm Dangling Pointer entstehen, da ja bei assign die Property nach dem Löschen auf ein nicht mehr existentes Objekt verweist. Dazu ein Beispiel:

thePerson.firstName = [[NSString alloc] initWithFormat: 
@"R%dD%d", 2, 2];
self.name = thePerson.firstName;
thePerson.firstName = nil;
NSLog(@"Name = %@", self.name);

Listing 2.89 Gefahren bei Propertys des Typs assign

Die Property firstName ist die einzige haltende Referenz auf die Zeichenkette, die der Code in der ersten Zeile erzeugt. Folglich führt die Zuweisung von nil in der dritten Zeile zur Zerstörung der Property. Wenn die Property name den Typ weak hat, setzt die Laufzeitumgebung diese Referenz bei der Freigabe ebenfalls auf nil, und die Log-Anweisung gibt

Name = (null)

aus. Hat die Property name hingegen den Typ assign, dann setzt die Laufzeitumgebung diese Referenz bei der Freigabe nicht auf nil, und die Log-Anweisung greift auf ein ungültiges Objekt zu. Das führt in der Regel zu einem Absturz des Programms.

Wie Sie an dem Beispiel sehen, vermeidet der Typ weak die Entstehung von Dangling Pointern, weshalb Sie weak gegenüber assign bevorzugen sollten, wobei Ihnen weak allerdings nur beim automatischen Referenzzählen zur Verfügung steht. Beim manuellen Referenzzählen können Sie leider nur assign verwenden. Da Xcode 5 für neue Projekte immer automatisches Referenzzählen verwendet und Sie auch ältere Projekte problemlos darauf umstellen können [Anm.: Sofern die erzeugten Apps mindestens unter iOS 5 laufen.] , stellt das jedoch in den meisten Fällen keine wesentliche Einschränkung dar.

Atomare Propertys sind gegen gleichzeitige Aufrufe aus unterschiedlichen Threads abgesichert. Normalerweise bekommen Sie damit selten Probleme, so dass Sie Ihre Propertys in der Regel nonatomic deklarieren können. Allerdings haben die synthetisierten Methoden zum Lesen atomarer Propertys noch einen interessanten Nebeneffekt: Sie legen das Objekt in den Autoreleasepool, bevor sie es zurückgeben.

Dieses Konstrukt ist in Situationen nützlich, in denen Sie zwischen dem Lesen und dem Verwenden eines Property-Wertes das haltende Objekt zerstören oder den Property-Wert neu setzen. Dieses Problem tritt beispielsweise in folgendem Code auf (siehe auch Abschnitt 2.3.3, »Autoreleasepools«):

__unsafe_unretained NSString *theFirstName = 
thePerson.firstName;
thePerson.firstName = nil;
NSLog(@"firstName=%@", theFirstName);

Listing 2.90 Dangling Pointers durch eine nicht-atomare Property

Bei einer nicht-atomaren Property kann die zweite Zeile zu einem Dangling Pointer für die Variable theFirstName führen, da die Freigabe der Person auch die Zeichenkette aus dem Speicher entfernen könnte. Bei der Synthetisierung einer atomaren Property tritt hier hingegen kein Dangling Pointer auf.

Die Accessoren und Attributzugriff

Sie sollten nur dann innerhalb des Getters und Setters direkt auf ein Attribut zugreifen, wenn Sie diese Methoden selbst implementieren. Andere Methoden (und Objekte) sollten den Attributwert immer über diese Accessoren lesen oder schreiben. Das schließt auch die Freigabe des Wertes ein. Setzen Sie dazu einfach den Wert über den Setter auf nil.

Durch die konsequente Verwendung der Getter und Setter können Sie so den Attributzugriff überschreiben: Subklassen können diese Methoden überschreiben und so das Verhalten der Methoden gegenüber dem der Methoden der Superklassen verändern.

Wenn Sie die Accessoren selbst implementieren, sollten Sie eine zu den Parametern passende Implementierung wählen. Wenn die Property-Deklaration beispielsweise den Parameter strong hat, sollte sie auch das referenzierte Objekt halten. Die Property-Deklaration beschreibt einen Vertrag. Halten Sie sich daran.



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