2.3 Speicherverwaltung, Propertys und Key-Value-Coding
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 durch.
Glücklicherweise hat sich die richtige Verwaltung des Speichers seit iOS 5 mit der Einführung des Automatic Reference Counting (ARC) wesentlich vereinfacht. Dabei sorgt in fast allen Fällen der Compiler dafür, dass mit dem Speicher alles rund läuft.
Um jedoch die automatische Speicherverwaltung richtig verstehen zu können, sollten Sie sich vorher mit der manuellen Speicherverwaltung auseinandersetzen. Zum einen können Sie so besser verstehen, warum ARC auch seine Grenzen hat und Sie helfend eingreifen müssen, und zum anderen finden Sie noch viel Sourcecode im Netz, der auf manuellem Referenzenzählen basiert. Allerdings hat auch die manuelle Speicherverwaltung von Objective-C nichts mit der fehlerträchtigen Speicherverwaltung zu tun, mit der man als C- oder C++-Programmierer kämpfen muss.
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. Da dieser Speicherbereich zur Programmlaufzeit eine feste Größe hat, brauchen Sie sich um dessen Verwaltung nicht zu kümmern.
2.3.1 Der Stack

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 darauf legen, 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. Da Sie jedoch den Stapelspeicher nicht explizit anfordern oder freigeben können, brauchen Sie sich auch nicht um dessen Verwaltung zu kümmern.
2.3.2 Der Heap

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 alle Objekte ab, deren Speicher sie über die Klassenmethode alloc reserviert. Anschließend existiert das neue Objekt als reservierter 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
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 selber 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.36 zeigt die Speicherbelegung eines Objekts der Klasse Model. Dabei liegt sowohl das Modellobjekt als auch dessen 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 können Sie auch sehen, 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 auf es 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.
Abbildung 2.36 Ein Modellobjekt des Beispielprogramms im Speicher
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 ein Handtuch, und für Sie ist keine mehr frei.
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.
2.3.3 Referenzen zählen

In Cocoa Touch gibt es feste Regeln für die Speicherverwaltung. Wenn Sie sich daran halten, können Sie die meisten Speicherverwaltungsfehler schon dadurch vermeiden. Die Speicherverwaltung basiert dabei auf dem Referenzenzählen, das das nachfolgende Beispiel veranschaulicht.
Ein Hausarzt schickt seinen Patienten ins Krankenhaus. Dort untersuchen und behandeln ihn verschiedene Fachärzte, indem er bei jedem Arzt die Sprechstunde besucht. Die Ärzte schicken den Patienten zu weiteren Ärzten im Krankenhaus, bestellen ihn zu erneuten Untersuchungen oder Behandlungen ein oder teilen ihm ihren Befund mit. Da der Patient glücklicherweise nicht schwer erkrankt ist, fallen die Befunde immer negativ aus beziehungsweise sind die Behandlungen immer erfolgreich. Die Frage ist nun: Wann weiß der Patient, dass er geheilt und entlassen ist?
Ein Arzt kann nicht alleine über die Entlassung des Patienten entscheiden. Wenn jedoch andererseits kein Arzt den Patienten entlässt, belegt der Patient weiter ein Bett im Krankenhaus, und irgendwann kann es keine weiteren Patienten mehr aufnehmen, da lauter gesunde Menschen die Betten belegen, die sich ihrerseits langweilen und das Pflegepersonal nerven. Wie kann die Klinikleitung dieses Problem lösen? Die Ärzte können sich natürlich regelmäßig abstimmen und zu gegebener Zeit dem Patienten seine Entlassung mitteilen. Das wäre indes sehr aufwendig, und die Ärzte verbrächten viel Zeit mit der Abstimmung, in der sie besser andere Patienten behandeln könnten. Eine andere Möglichkeit besteht darin, dem Patienten einen Laufzettel zu geben. Darauf bekommt jeder Arzt, den der Patient noch aufsuchen muss, einen Strich. Wenn ein Arzt mit der Behandlung des Patienten fertig ist, entfernt er diesen Strich wieder von dem Zettel. Der Patient darf nach Hause gehen, wenn er keinen Strich mehr auf dem Zettel hat.
In diesem Beispiel ist der Patient ein Bild für ein dynamisch erzeugtes Objekt, die Ärzte sind Verweise auf dieses Objekt, und die Entlassung aus dem Krankenhaus entspricht der Freigabe des Objekts aus dem Speicher. Und so funktioniert es auch in Cocoa Touch: So, wie die einzelnen Ärzte den Patienten nicht entlassen können, können die einzelnen Referenzen nicht entscheiden, wann das von ihnen referenzierte Objekt freigegeben werden soll.
Wenn das Programm das Objekt zu früh freigibt, entsteht ein Dangling Pointer. Im Krankenhausbeispiel würde ein Arzt vergebens auf den Patienten warten, der schon zu Hause gemütlich im Sessel sitzt. Analog entsteht im Hauptspeicher ein Leck, wenn keine Referenz das referenzierte Objekt endgültig freigibt. Das entspricht dem gesunden Patienten im Krankenhaus, den kein Arzt mehr sehen will. Da er nie die Information bekommt, dass er gehen darf, liegt er bis zum Sankt-Nimmerleins-Tag im Krankenhaus.
Die zentrale Frage der Speicherverwaltung dreht sich also darum, wann ein Objekt freigegeben werden kann. Verweise in Objective-C beziehungsweise C sind einfach nur Speicheradressen, die von beliebigen Programmbereichen auf das Objekt zeigen können. Eine Lösung, die auf die selbstständige Abstimmung dieser Zeiger setzt, kommt deswegen nicht infrage. Die Ärzte im Krankenhaus haben ja auch Besseres zu tun, als sich ständig darüber auszutauschen, welcher Patient nun nach Hause darf und welcher noch bleiben muss. Diesen Mehraufwand würde keine gesetzliche Krankenkasse bezahlen, und da nun mal nicht jeder Mitglied der privaten Krankenversicherungen ist, muss also eine andere Lösung her.
Objective-C verwendet auch eine Strichliste in Form eines Zählers, sodass sich jedes Objekt selber merken kann, wie viele Verweise auf es zeigen. Im Gegensatz zum Laufzettel des Patienten zählt das Objekt allerdings nur die Anzahl der Verweise. Es merkt sich nicht, welche Verweise auf es zeigen. Der Zähler reicht jedoch vollkommen aus, um festzustellen, ab wann es keinen Verweis mehr auf das Objekt gibt, und dann kann es sich selber aus dem Hauptspeicher entfernen.
Die folgenden Abbildungen zeigen dieses Prinzip. Jede der drei Personen (Objekte) besitzt einen Referenzzähler (Reference Counter). Jeder neue Verweis auf das Objekt kann diesen Referenzzähler erhöhen, damit das Objekt weiß, dass eine weitere Referenz existiert. Beim Wegfall dieses Verweises muss dann der Referenzzähler dekrementiert werden. Das Objekt kann nun feststellen, wann das Programm den letzten Verweis auf es löst, und sich aus dem Speicher entfernen. In Abbildung 2.37 zeigen Fred und Jeff auf Ophelia, die dementsprechend einen Referenzzähler von 2 hat.
Abbildung 2.37 Ophelia erfreut sich großer Beliebtheit.
Mit der Zeit merkt Jeff, dass er bei Ophelia keine Chancen mehr hat und Celia sowieso viel netter ist. Er lässt Ophelia fallen, und der Verweis von Jeff auf Ophelia fällt weg. Sie besitzt daher nur noch einen Referenzzähler von 1.
Abbildung 2.38 Ophelia, pass auf, du hast nur noch den Fred!
Jetzt ist Ophelia nicht mehr so beliebt, und da Fred ihre Spielchen auch langsam satt, lässt sein Interesse an ihr auch stark nach. Deswegen entfernt auch Fred seinen Verweis auf sie, womit sowohl die Beliebtheit als auch der Referenzzähler von Ophelia auf 0 sinkt. Sie stellt dabei fest, dass niemand sie mehr braucht, entfernt sich somit aus dem Speicher und verwandelt sich in das in der Abbildung gezeigte Logikwölkchen im Datennirwana. Arme Ophelia.
Abbildung 2.39 Ophelia, du bist raus.
Der Referenzenzähler
Das Beispiel gibt Werte für die Referenzenzähler nur zur Veranschaulichung an. In der Praxis sehen Sie selten so einen einfachen Werteverlauf. Der Sinn des Referenzenzählens ist es ja gerade, dass beliebige Verweise auf Ihre Objekte bestehen können. Außerdem gibt es Objekte – wie beispielsweise Zeichenkettenkonstanten oder Singletons –, die das Referenzenzählen nur vortäuschen.
Langer Rede kurzer Sinn: Gehen Sie niemals von bestimmten Werten für den Referenzenzähler aus. Das taugt nicht zur Analyse der Speicherverwaltung oder gar zur Suche nach Speicherverwaltungsfehlern.
Das Verwalten der Referenzenzähler macht genau den Unterschied zwischen manueller und automatischer Speicherverwaltung. Bei der manuellen Variante gibt es die drei Methodenaufrufe retain, release und autorelease. Die Cocoa-Wurzelklassen, von denen Sie in der Regel nur NSObject für Ihre Klassen verwenden, stellen diese Methoden bereit.
Um den Referenzzähler eines Objekts um eins zu erhöhen, senden Sie ein retain an das Objekt, und mit release können Sie den Referenzzähler um eins verringern. Als Aufrufer dieser Methoden sollten Sie sich nicht darum kümmern, wie viele Verweise auf ein Objekt zeigen.
Dieses Verhalten entspricht übrigens dem Umgang mit Dateien im Dateisystem Ihres Computers. Auch dort ist für das Vorhandensein einer Datei maßgeblich, wie viele Verweise auf diese Datei zeigen. Wenn Sie eine Datei anlegen, gibt es zunächst genau einen Verweis auf die Datei, nämlich von dem Verzeichnis, in dem die Datei liegt. Das Verzeichnis weiß, dass diese Datei in ihm existiert, und erlaubt den Zugriff auf diese Datei. Die Anzahl der Verweise kann sich erhöhen, wenn Sie oder das Betriebssystem Hardlinks auf die Datei setzen. Die Anzahl der Verweise verringert sich, wenn Sie Hardlinks wieder entfernen. [Zwischen der ursprünglichen Datei und einem Hardlink besteht kein Unterschied. Sie können auch zuerst die Datei löschen, ihr Inhalt bleibt so lange erhalten, wie es noch Hardlinks darauf gibt. Machen Sie das aber bloß nicht mit Softlinks!] Ist die Anzahl der Verweise bei 0 angekommen, löscht das Betriebssystem auch den Dateiinhalt. Konsequenterweise ist der Systemaufruf zum Löschen einer Datei unlink und nicht delete. Das Konzept des Referenzzählens ist also nicht nur auf die iOS-Programmierung beschränkt, sondern ein gängiges Mittel, um die Existenzberechtigung von Objekten in komplexen Systemen zu verwalten.
Die Methode autorelease markiert ein Objekt für einen späteren Aufruf der Methode release. Sie dürfen das Objekt also nach dem Aufruf von autorelease noch verwenden. Sie legt das Objekt in den Autorelease-Pool. Jeder Aufruf von autorelease legt das Objekt ein weiteres Mal in den Autorelease-Pool, sodass das Objekt für jedes autorelease jeweils ein release erhält.
2.3.4 Die Speicherverwaltungsregeln

Durch die vorgenannten Methodenaufrufe legen Sie bei manuellem Referenzenzählen also fest, ob ein Verweis das referenzierte Objekt im Speicher hält oder nicht. Wenn eine Referenz das Objekt nicht halten soll, brauchen Sie dafür auch keine der genannten Methoden aufzurufen. Aus Sicht der Speicherverwaltung sind also nur die haltenden Referenzen interessant. Wann jedoch hält eine Referenz ein Objekt?
Apple hat dafür vier einfache Regeln aufgestellt:
- Eine Referenz hält ein Objekt, wenn der Name seiner Erzeugungsmethode mit alloc, new, copy oder mutableCopy beginnt.
Erzeugen Sie hingegen ein Objekt über eine Methode, deren Name nicht auf diese Regel passt, dann ist die Referenz kein Halter des Objektes. Sie sollten das auch unbedingt bei der Benennung Ihrer eigenen Methoden beachten. - Wenn Sie die Methode retain eines Objekts aufrufen, ist der Verweis ein Halter des Objekts.
- Für jeden haltenden Verweis auf ein Objekt müssen Sie entweder die Methode release oder autorelease genau einmal aufrufen.
- In der Methode dealloc sollten Sie immer alle Verweise auf andere Objekte freigegeben, die die Attribute des Objekts halten. Die Laufzeitumgebung ruft diese Methode unmittelbar vor der Freigabe des Speichers auf. Sie sollten in dieser Methode als Letztes immer die dealloc-Methode der Superklasse aufrufen. Sie dürfen allerdings sonst dealloc niemals direkt aufrufen.
Die erste Regel gilt übrigens nicht nur für die Methoden aus der Klasse des Objekts, sondern für alle Methoden. Dabei ist die Methode alloc die einzig wirkliche Erzeugungsmethode für Objekte. Die anderen Methoden geben nur über alloc erzeugte Objekte zurück. Diese Regel gilt übrigens auch, wenn die Methode das Objekt nicht neu erzeugt, wie das beispielsweise bei einem Objektcache der Fall sein kann.
Tipp
Die Speicherverwaltungsregeln sind fundamental. Nehmen Sie sich die Zeit, um sich damit ausreichend vertraut zu machen. Es zahlt sich aus. Bedenken Sie auch, dass die erste Regel eine Namenskonvention ist. Sie erlaubt Ihnen zum einen, erzeugte Objekte richtig zu verwalten. Andererseits gibt sie Ihnen auch ein Schema vor, wie Sie Ihre Methoden zu benennen haben.
Die folgenden Beispiele sollen Ihnen die Regeln veranschaulichen:
NSNumber *theAge = [[NSNumber alloc] initWithInt:15];
NSLog(@"age=%@", theAge);
[theAge release];
Listing 2.74 Objekterzeugung über eine »alloc-init«-Kette
Hier passt der Aufruf in der ersten Zeile auf die erste Regel. Der Verweis theAge hält also die Zahl. Sie müssen das Objekt deshalb über die Methode release freigeben. Ein Beispiel, bei dem die erste Regel nicht zutrifft, ist das folgende:
NSNumber *theAge = [NSNumber numberWithInt:15];
NSLog(@"age=%@", theAge);
Listing 2.75 Objekterzeugung über einen Convenience-Konstruktor
Der Methodenname in der ersten Zeile passt nicht zur ersten Regel. Die Variable theAge hält also die Zahl nicht, und Sie dürfen dem Objekt kein release schicken. Klassenmethoden wie die in der ersten Zeile nennt man übrigens auch Convenience-Konstruktoren.
Für eine Methode, die ein neues Objekt erzeugt, gibt es also zwei Möglichkeiten. Wenn ihr Name zur ersten Regel passt, muss sie das Objekt halten, das sie zurückgibt.
- (NSNumber *)newAge {
return [[NSNumber alloc] initWithInt:15];
}
- (void)printAge {
NSNumber *theAge = [self newAge];
NSLog(@"age=%@", theAge);
[theAge release];
}
Listing 2.76 Objekthaltende Erzeugung über eine Methode
In diesem Beispiel gibt die Methode newAge die Halterschaft an die Variable theAge in der aufrufenden Methode printAge ab, und Sie müssen das Objekt über diese Variable freigeben. Alternativ können Sie auch einen Namen wählen, der nicht auf die erste Regel passt. Dann sollte die Methode so aussehen:
- (NSNumber *)age {
return [NSNumber numberWithAge:15];
}
- (void)printAge {
NSNumber *theAge = [self age];
NSLog(@"age=%@", theAge);
}
Listing 2.77 Objekterzeugung über eine Methode mit einen Convenience-Konstruktor
Die aufrufende Methode printAge darf das Objekt in diesem Fall also nicht durch einen Aufruf von release freigeben.
Dieses Beispiel verwendet einen Convenience-Konstruktor für die Objekterzeugung. Viele Cocoa-Klassen stellen jedoch keinen solchen Konstruktor zur Verfügung. In diesem Fall muss die Methode das Objekt auch durch einen alloc-init-Aufruf erzeugen. Die aufrufende Methode darf allerdings aufgrund der Namenskonvention kein Halter des Objekts sein. Die Methode muss also das Objekt freigeben. Sie dürfen es jedoch nach einem Aufruf von release nicht mehr verwenden, da es dadurch ja bereits aus dem Speicher gelöscht worden sein könnte.
Aus diesem Grund müssen Sie autorelease verwenden, sodass der release-Aufruf erst zu einem späteren Zeitpunkt erfolgt.
- (NSNumber *)age {
NSNumber *theAge = [[NSNumber alloc] initWithInt:15];
return [theAge autorelease];
}
Listing 2.78 Objekterzeugung über eine Methode und den Autorelease-Pool
Die Beispiele oben erläutern die Speicherverwaltungsregeln auf Methodenebene. Die folgenden Beispiele veranschaulichen die Regeln auf Klassenebene, wenn Sie Attribute verwenden. Hier ist die Deklaration für die Beispielklasse:
@interface Person : NSObject {
@private
NSNumber *age;
}
– (NSNumber *)age;
– (void)setAge:(NSNumber *)inAge;
@end
Listing 2.79 Klasse mit Methoden für den Attributzugriff
Der Getter age hat eine einfache Implementierung, da Sie hier keine Speicherverwaltungsoperationen durchführen müssen:
-(NSNumber *)age {
return age;
}
Listing 2.80 Methode zum Lesen eines Attributwertes
Ein Objekt der Klasse Person hält ja bereits die Zahl für das Alter, und der Methodenname passt nicht zur ersten Regel. Also brauchen Sie sich hier nicht weiter um die Speicherverwaltung zu kümmern.
Die vierte Regel fordert, dass die Methode dealloc alle Verweise auf die gehaltenen Objekte freigibt. Außerdem soll sie die Methode der Superklasse aufrufen. Für dieses Beispiel sieht diese Methode also so aus:
- (void)dealloc {
[age release];
[super dealloc];
}
Listing 2.81 Freigabe eines Attributwertes in der Methode »dealloc«
Sie müssen jetzt nur noch die Methode zum Setzen des Alters implementieren. Hier können Sie hingegen nicht nur einfach dem Attribut age den neuen Wert zuweisen. Es könnte ja bereits einen Wert besitzen. Diesen Wert müssen Sie natürlich zuerst freigeben.
- (void)setAge:(NSNumber *)inAge {
[age autorelease]; // release kann Dangling Pointer erzeugen
age = [inAge retain];
}
Listing 2.82 Setzen eines Attributwerts
Vielleicht wundern Sie sich darüber, dass die Methode in der zweiten Zeile autorelease anstatt release verwendet. Allerdings könnte release an dieser Stelle zu einem Laufzeitfehler führen, wenn age und inAge bereits vor dem Aufruf auf das gleiche Objekt verweisen (siehe Abbildung 2.40).
Abbildung 2.40 Freigabe des Verweises »age« über »release«
Ein release gäbe dieses Objekt hingegen unmittelbar frei, wenn das Attribut sein letzter Halter wäre, und es entstünde ein Dangling Pointer. Der nachfolgende retain-Aufruf würde hier wahrscheinlich direkt zu einem Laufzeitfehler führen. Durch die Verwendung von autorelease bleibt das Objekt zunächst erhalten, und der release-Aufruf erfolgt erst später. Das vermeidet einen Dangling Pointer in der dritten Zeile. Sie können die Methode auch ohne autorelease formulieren, indem Sie zuerst das retain und danach das release senden:
- (void)setAge:(NSNumber *)inAge {
[inAge retain];
[age release];
age = inAge;
}
Listing 2.83 Setter mit Vertauschung von »retain« und »release«
Sie können auch einfach das Attribut und den Parameter miteinander vergleichen (siehe Listing 2.84). Diese Variante hat den Vorteil, dass die Methode nur die wirklich notwendigen Speicherverwaltungsoperationen ausführt.
- (void)setAge:(NSNumber *)inAge {
if(age != inAge) {
[age release];
age = [inAge retain];
}
}
Listing 2.84 Setter mit »if«-Abfrage
Vorsicht
Sie sollten bei der Verwendung eines Setters niemals davon ausgehen, dass er eine bestimmte Variante verwendet. Das ist ein Implementierungsdetail, und die gehen Sie nichts an. Bei der ersten Variante überlebt der alte Wert immer den Setter-Aufruf, weil Sie ihn ja in den Autorelease-Pool legen. Bei den beiden anderen Varianten ist das allerdings nicht der Fall.
Was passiert hingegen in der Methode setAge:, wenn der neue Wert nil ist oder wenn das Attribut age auf nil verweist? In Objective-C können Sie an nil jede beliebige Nachricht senden, und das ist im Gegensatz zu vielen anderen Programmiersprachen kein Fehler. Ein solcher Methodenaufruf führt keine Anweisungen aus, und das Ergebnis ist immer nil oder 0, wenn die Methode einen primitiven Datentyp zurückgibt.
Sie können also auch die Methode setAge: in dealloc für die Freigabe des Age-Objekts verwenden.
- (void)dealloc {
[self setAge:nil];
[super dealloc];
}
Listing 2.85 Freigabe eines Attributwertes durch Verwendung des Setters
Die Verwendung der Methode statt der direkten Freigabe durch release hat den Nebeneffekt, dass sie das Attribut auf einen definierten Wert setzt. Es verweist danach nicht mehr auf den ungültigen Speicherbereich des freigegebenen Objekts. In der Methode dealloc ist die Auswirkung nicht so groß. Falls Sie hingegen ein Attribut an einer anderen Stelle freigeben wollen, macht das natürlich schon einen Unterschied.
Accessoren und manuelle Speicherverwaltung
Durch die Verwendung des Setters können Sie also eine Referenz auf ein Objekt in einem Schritt freigeben und ihr einen neuen, definierten Wert zuweisen. Das erleichtert die manuelle Speicherverwaltung erheblich und erhöht auch die Lesbarkeit des Codes. Sie sollten also möglichst Setter-Aufrufe gegenüber direkten Attributzugriffen bevorzugen.
Diese Methoden können Sie auch über die Punktnotation aufrufen, die es seit Objective-C 2.0 gibt. Sie lehnt sich an die Syntax anderer objektorientierter Sprachen an. Mit Punktnotation sieht die dealloc-Methode dann so aus:
- (void)dealloc {
self.age = nil;
[super dealloc];
}
Listing 2.86 Freigabe eines Attributwertes mit Punktnotation
In Abschnitt 2.2 haben wir Ihnen von der Verwendung von Attributdeklarationen in Klassen abgeraten, weil sie eine Verletzung des Geheimnisprinzips darstellen. Bei der manuellen Speicherverwaltung kommt noch der Umstand hinzu, dass Sie sich darum im Setter kümmern müssen. Die Verwendung von Propertys ist hier wesentlich einfacher.
Wenn das Programm ein Objekt für den weiteren Verlauf nicht mehr benötigt, muss man bei manueller Speicherverwaltung den vom Objekt verwendeten Speicher freigeben, um nicht unnötig Ressourcen zu belegen. Das Freigeben des Speichers erfolgt über dabei die Methode release.
Die Nachricht release kann dabei den Aufruf der Methode dealloc bewirken, über die ein Objekt seine verwendeten Ressourcen freigibt. Das sind in der Regel andere, vom Objekt referenzierte Objekte. Sie sollten diese Methode jedoch niemals direkt aufrufen, da das ausschließlich die Methode release übernimmt.
Eine typische Implementierung von dealloc sieht so aus wie in Listing 2.86. Sie geben zuerst die Ressourcen frei und rufen danach die Implementierung der Superklasse auf. Das ist allerdings nur bei manuellem Referenzenzählen notwendig und erlaubt. Bei der automatischen Variante ist der Aufruf ein Fehler, den der Compiler mit einer entsprechenden Meldung quittiert.
Garbage Collection
Auf dem Mac gibt es seit OS X 10.5 (Leopard) eine automatische Garbage Collection. Dabei muss sich der Programmierer keine Sorgen mehr um das Freigeben von Speicher machen, da der Garbage Collector automatisch alle Objekte freigibt, auf die keine Referenzen mehr zeigen. Er sammelt also den Objektmüll aus dem Speicher und wirft ihn weg. So angenehm eine Garbage Collection auch ist; es gibt dazu keine Entsprechung unter iOS. Das liegt daran, dass das Müllsammeln extrem rechenaufwendig ist. Ab iOS 5 geht Apple deshalb mit dem automatischen Referenzenzählen einen anderen Weg, der eine Synthese aus der Bequemlichkeit der Garbage Collection und der Geschwindigkeit des Referenzenzählens ist. Aber keine Sorge, Sie werden auch ohne Garbage Collection gut zurechtkommen.
2.3.5 Autorelease-Pools

Autorelease-Pools (ARP) vereinfachen die Speicherverwaltung auch schon bei manuellem Referenzenzählen sehr stark, da Sie damit in vielen Fällen die Objekte dann freigeben können, wenn es für Sie günstig ist. Sie brauchen also diesen Objekten kein release mehr zu schicken, wenn Sie es wirklich nicht mehr benötigen.
Die Funktionsweise des Autorelease-Pools ist relativ einfach. Er ist eine Sammlung von beliebigen Objekten. Sie können ein Objekt zu dieser Sammlung hinzufügen, indem Sie dessen Methode autorelease aufrufen. Das können Sie beliebig oft machen, [Natürlich sollten Sie sich dabei aber an die Speicherverwaltungsregeln halten, um keine Dangling Pointer zu erzeugen.] sodass der Autorelease-Pool jedes Mal einen neuen Verweis auf dieses Objekt in seine Sammlung einfügt. Der Autorelease-Pool kann also jedes Objekt beliebig oft enthalten. Wenn der Pool ein Objekt in seine Sammlung aufnimmt, verändert er allerdings nicht dessen Referenzenzähler.
Übernimm du mal
Durch das Einfügen eines Objekts in den Autorelease-Pool geben Sie die Halterschaft des Objekts von Ihrem Code an den Pool ab. Auf das Krankenhausbeispiel bezogen bedeutet das, dass ein Arzt die Behandlung des Patienten an einen Fachkollegen abgibt, damit er Golf spielen kann. Der Fachkollege untersucht den Patienten und sorgt für die Entfernung des Striches auf dem Laufzettel.
Irgendwann zerstört das Programm den Autorelease-Pool, der dann an jedes Objekt in der Sammlung ein Release sendet. Dabei bekommt jedes Objekt genauso viele Release- Aufrufe, wie es vorher Autorelease-Aufrufe erhalten hat. Die Zerstörung des Autorelease-Pools 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.
Bislang brauchten Sie sich um die Erzeugung und Zerstörung von Autorelease-Pools nicht zu kümmern. Es gibt jedoch einige Situationen, in denen das notwendig ist.
Es gibt zwei Wege, Autorelease-Pools zu verwalten. In allen Versionen bis Xcode 4.1 einschließlich können Sie Autorelease-Pools nur über die Klasse NSAutoreleasePool erzeugen. Das machen Sie, indem Sie ein Objekt dieser Klasse anlegen und im selben Block auch wieder freigeben. Alle Aufrufe von autorelease, die zwischen diesen beiden Anweisungen stattfinden, nutzen dann diesen Pool.
NSAutoreleasePool *thePool = [[NSAutoreleasePool alloc] init];
...
[thePool drain];
Listing 2.87 Anlegen eines Autorelease-Pools über die Foundation-Klasse
Die Nachricht drain an den Autorelease-Pool hat unter iOS die gleiche Wirkung wie release. Der Unterschied dieser beiden Methoden macht sich nur unter OS X bei einem Programm mit Garbage Collection bemerkbar. Sie dürfen also unter iOS auch ruhig release verwenden. Die Verwendung von drain hat sich allerdings inzwischen eingebürgert.
Xcode ab Version 4.2 unterstützt die Direktive @autoreleasepool. Sie klammern in dem Block dieser Direktive einfach die Anweisungen, die diesen Pool verwenden sollen.
@autoreleasepool {
...
}
Listing 2.88 Anlegen eines Autorelease-Pools über die Direktive
In welchen Pool soll ich nun springen?
Die Verwendung von NSAutoreleasePool ist die veraltete Form. Sie wurde hier aufgeführt, weil sie noch sehr häufig vorkommt. Sie können und sollten in Ihren Programmen lieber die Direktive @autoreleasepool verwenden. Zwar verstehen sie nur die Compiler ab Xcode 4.2 (beziehungsweise LLVM ab Version 3); der erzeugte Code läuft jedoch unter allen iOS-Versionen. Laut Apple sind die Autorelease-Pools über die Direktive auch performanter. Das automatische Referenzenzählen (siehe Abschnitt 2.4) verbietet sogar die explizite Verwendung von NSAutoreleasePool. Ab Xcode 4.2 sollten Sie also auf die Verwendung von NSAutoreleasePool zugunsten von @autoreleasepool verzichten.
Es gibt drei wichtige Situationen, in denen Sie einen Pool selber verwalten müssen:
- in der main-Funktion,
- bei der Verwendung von Threads,
- und um Speicherüberläufe zu vermeiden.
Die main-Funktion
Die main-Funkion legt Xcode bei der Erzeugung eines neuen Projekts automatisch für Sie an. Die Funktion sieht dann ab Xcode 4.2 ungefähr so aus:
int main(int argc, char *argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, nil);
}
}
Listing 2.89 Eine »main«-Funktion mit »@autoreleasepool«
Ältere Xcode-Versionen verwenden die Klasse NSAutoreleasePool, und die Funktion hat in etwa diese Gestalt:
int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = 
[[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, nil);
[pool release];
return retVal;
}
Listing 2.90 Eine »main«-Funktion mit »NSAutoreleasePool«
Niemals ohne Autorelease-Pool
In Objective-C sollte die main-Funktion immer einen Autorelease-Pool verwalten, auch wenn Sie ihn anscheinend nicht verwenden. Cocoa verlässt sich auf die Existenz des Pools, und daher kann die Verwendung einer Cocoa-Klasse außerhalb eines Pools zu folgender Fehlermeldung führen:
Object 0xXXX of class YYY autoreleased with no pool in place – just leaking – break on objc_autoreleaseNoPool() to debug
Threads
Ein Thread ist ein eigenständiger Ausführungsstrang in einem Programm. Über Threads können Sie erreichen, dass Ihr Programm mehrere Aufgaben gleichzeitig erledigt. Beispielsweise kann Safari unter OS X eine Datei herunterladen, während Sie mit dem Browser durch das Internet surfen. In der Regel brauchen Sie Threads allerdings nur in seltenen Fällen. In Cocoa Touch gibt es häufig elegantere und effizientere Möglichkeiten, um nebenläufige Programme zu schreiben. Multithreading macht in vielen Fällen auch erst auf Mehrkern- beziehungsweise Mehrprozessorsystemen [Unter iOS sind das zurzeit die Geräte mit mindestens einem A5-Prozessor.] Sinn.
Wie dem auch sei, jeder Thread braucht einen eigenen Autorelease-Pool. Am einfachsten können Sie einen Thread über die Methode performSelectorInBackground:withObject: starten, wie Listing 2.91 zeigt.
- (void)startParsingWithFile:(NSString *)inPath {
[self perfomSelectorInBackground:@selector(parseWithFile:) 
withObject:inPath];
}
- (void)parseWithFile:(NSString *)inPath {
@autoreleasepool {
...
}
}
Listing 2.91 Starten eines Threads
Die Methode startParsingWithFile: startet dabei die Methode parseWithFile: in einem eigenen Thread, wobei Letztere einen eigenen Autorelease-Pool besitzt.
Vermeidung von Speicherüberläufen
Die exzessive Nutzung des Autorelease-Pools, also wenn zu viele Objekte im Pool liegen, kann zu einem Speicherüberlauf führen. Das kann auch passieren, obwohl Sie sich strikt an die Speicherverwaltungsregeln gehalten haben, und liegt daran, dass der Autorelease-Pool alle Objekte über seine gesamte Lebenszeit hält. Häufig ist das indes nicht notwendig, und der Pool könnte zumindest einen Teil der Objekte auch früher freigeben.
Sie können zwar dem Autorelease-Pool nicht explizit mitteilen, welche Objekte Sie freigeben wollen. Sie können jedoch Autorelease-Pools ineinander verschachteln. Dann sammelt immer der innerste Pool die Objekte auf, denen Sie ein autorelease senden. Wenn Sie diesen inneren Pool freigeben, gibt er auch alle enthaltenen Objekte frei. Dadurch können Sie verhindern, dass der Berg der nicht mehr verwendeten Objekte zu stark anwächst. Da dieses Problem häufig bei großen Schleifen auftritt, reicht es in der Regel aus, einen Autorelease-Pool innerhalb der Schleife zu platzieren.
- (void)parseFiles:(NSArray *)inFiles {
for(NSString *theFile in inFiles) {
@autoreleasepool {
[self parseFile:theFile];
}
}
}
Listing 2.92 Geschachtelter Autorelease-Pool
Die Schleife in Listing 2.92 enthält einen geschachtelten Autorelease-Pool, sodass die Methode alle temporären Objekte am Ende jedes Schleifendurchlaufs freigibt.
2.3.6 Propertys und Accessoren

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 enthält alle möglichen Parameter. Sie sind nach Gruppen unterteilt aufgelistet.
Sie können diese Parameter in einer Property-Deklaration durch Kommata 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) sollten Sie hingegen keinen Speicherverwaltungsparameter angeben.
Die Parameter weak und strong sind nur zusammen mit dem automatischen Referenzenzählen verfügbar, das in Abschnitt 2.4 behandelt wird.
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(strong, readonly) NSString *name;
und in die anonyme Kategorie fügen Sie die hervorgehobene Zeile aus Listing 2.93 ein.
@interface Model()
@property (strong, readwrite) NSString *name;
@property (nonatomic, strong) NSMutableArray *objects;
@end
Listing 2.93 Redeklaration einer Readonly-Property als schreibbar
Wenn Sie Propertys über @synthesize implementieren, dann legt der Compiler die Zugriffsmethoden entsprechend der aufgeführten Parameter an. Er sorgt also beispielsweise dafür, dass der Setter den Wert der Property bei einer Zuweisung kopiert, falls Sie den Parameter copy verwendet haben. Wenn Sie hingegen die Accessoren selber implementieren, sollten Sie eine zu den Parametern passende Implementierung wählen. Die Property-Deklaration beschreibt einen Vertrag. Halten Sie sich daran.
Hier sind einige Property-Deklarationen angegeben, wie sie in der 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(assign) Person *spouse;
Listing 2.94 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 wird bei booleschen Attributen häufig isAttribute anstelle von attribute für den Methodennamen verwendet. 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 ja 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 retain, so kann der Inhalt dieser Propertys verändert werden, ohne die Methoden zum Setzen aufzurufen.
theName = [NSMutableString stringWithString:@"Fritz"];
thePerson.firstName = theName;
[theName setString:@"Frank"];
Listing 2.95 Ä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 retain 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.
Die Verwendung von copy verhindert dieses – in den meisten Fällen unerwünschte – Verhalten, da 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 übrigens kein NSMutableString, sondern immer ein NSString und somit vor Veränderungen geschützt.
Bei NSURL gibt es die Problematik mit veränderlichen Objekten nicht, sodass Sie hier getrost retain oder strong verwenden können. Sie können jedoch 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. [Das Protokoll NSCopying deklariert diese beiden Methoden.] Allerdings sendet die Methode noch ein retain, da ja der Methodenname auf die erste Speicherverwaltungsregel passt. Da das Programm das Objekt ja nicht verändern kann und es also während seiner Lebenszeit immer den gleichen Zustand hat, reicht das als »Kopie« vollkommen aus.
- (id)copyWithZone:(NSZone *)inZone {
return [self retain];
}
Listing 2.96 Erzeugen einer Kopie in nicht veränderlichen Klassen
Für die Ehegatten-Property spouse müssen Sie hingegen den Speicherverwaltungstyp assign oder weak (bei automatischem Referenzenzählen) verwenden. Da die Ehegatten gegenseitig aufeinander verweisen, entsteht eine zyklische Referenz. Bei diesen Referenzen dürfen Sie keinesfalls retain verwenden, da ansonsten Speicherlecks trotz Einhaltung der Speicherverwaltungsregeln entstehen. Wenn beispielsweise Fritz mit Erna verheiratet ist, dann verweist Fritz auf Erna und umgekehrt. Wenn nun diese Verweise den Typ retain hätten, dann würde 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 immer noch gegenseitig halten. Ist das nicht romantisch?
Atomare Propertys sind gegen gleichzeitige Aufrufe aus unterschiedlichen Threads abgesichert. Normalerweise bekommen Sie damit selten Probleme, sodass Sie Ihre Propertys in der Regel nonatomic deklarieren können. Allerdings haben die durch @synthesize erzeugten Methoden zum Lesen atomarer Propertys noch einen interessanten Nebeneffekt. Sie legen das Objekt in den Autorelease-Pool, bevor sie es zurückgeben.
- (NSNumber *)age {
// Anweisung zum Sperren
NSNumber *theResult = [[age retain] autorelease];
// Anweisung zum Entsperren
return theResult;
}
Listing 2.97 Aufbau der Methode zum Lesen einer atomaren Property
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:
NSString *theFirstName = thePerson.firstName;
[thePerson release];
NSLog(@"firstName=%@", theFirstName);
Listing 2.98 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 synthetischen Erzeugung einer atomaren Property kann hier kein Dangling Pointer auftreten. Auch hier gilt jedoch die gleiche Empfehlung wie für Setter: Sie können der Property-Deklaration die Getter-Implementierung nicht ansehen. Daher sollten Sie den Wert lieber explizit durch entsprechende Aufrufe von retain und release beziehungsweise autorelease schützen. Im Listing können Sie dazu einfach nach der ersten Zeile die folgende Zeile einfügen:
[[theFirstName retain] autorelease];
Listing 2.99 Einfügen eines Objekts in den Autorelease-Pool
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.
2.3.7 Key-Value-Coding

Den Zugriff auf die Attribute eines Objektes über Propertys oder traditionelle 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 Atributs 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.100 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.101 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 sie zu nutzen. Dabei können Sie über KVC sowohl auf Accessoren (egal ob sie synthetisiert oder selbst erstellt 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.41 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.102. Darin setzt die letzte Anweisung Fred als den neuen Manager in der Software-Entwicklungsabteilung.
Abbildung 2.41 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.102 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 aufzuaddieren, können Sie auch direkt eine Aggregation verwenden. Das sind KVC-Operatoren, mit denen Sie mehrere Werte zu einem zusammenfassen können:
NSNumber *theSum = [theManager
valueForKeyPath:@"department.employees.@sum.salaray"];
NSNumber *theAverage = [theManager
valueForKeyPath:@"department.employees.@avg.salaray"];
Listing 2.103 Berechnung der Gehaltssumme und des Durchschnittsgehalts
Neben der Summe @sum und dem Durchschnitt @avg stehen Ihnen noch 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.
2.3.8 Abschließende Bemerkungen
Propertys sind eine gute Möglichkeit, um die Speicherverwaltung in einer iOS-App zu vereinfachen. Sie geben nicht nur den Speicher frei, sondern setzen auch den Verweis auf nil. Das ist besonders praktisch, wenn die Freigabe eines Objekts außerhalb der dealloc-Methode erfolgen soll. Die Beispiele aus diesem Buch verwenden möglichst immer Propertys für die Speicherverwaltung.
Sie sollten alle Propertys, also auch die mit dem Speicherverwaltungstyp assign, für die Freigabe auf nil setzen. Sie können dann den Speicherverwaltungstyp später ändern, ohne sich Gedanken über Speicherlecks oder Dangling Pointer machen zu müssen. Voraussetzung dafür ist natürlich, dass Sie auch sonst nirgends ein retain, release oder autorelease an das Attribut senden. Das ist außerhalb der Accessoren des Attributs auch eine schlechte Idee.
Das gilt erst recht für Propertys. Senden Sie niemals eine dieser Methoden an eine Property, denn dabei wird der Wert der Property nicht gelöscht. Wenn Sie später dieser Property einen neuen Wert zuweisen, sendet der Setter der Property ein weiteres release zu dem alten Wert. Das bringt Ihre Speicherverwaltung natürlich vollkommen durcheinander.
Die implementierte Methode setAge: legt den alten Attributwert auf den Autorelease-Pool, um einen Dangling Pointer zu vermeiden. Automatisch erzeugte Methoden umgehen diese Problematik, indem sie die Zeigerwerte für den alten und den neuen Attributwert miteinander vergleichen. Sie sollten sich also nicht darauf verlassen, dass der alte Wert im Autorelease-Pool liegt.
Es gibt noch eine weitere Methode aus dem Bereich des Referenzenzählens. Sie heißt retainCount und steht Ihnen allerdings nur beim manuellen Referenzenzählen zur Verfügung. Damit können Sie den Wert des Referenzenzählers eines Objekts auslesen. Diese Methode wird hier der Vollständigkeit halber erwähnt. Sie sollten diese Methode jedoch nicht zur Überprüfung oder gar zur Steuerung Ihrer Speicherverwaltung verwenden. Es gibt mehrere Gründe, die dagegen sprechen:
- Das Referenzenzählen dient dazu, beliebig viele Verweise auf ein Objekt zu verwalten. Es gibt also häufig Verweise, die Sie nicht kennen. Diese Verweise verändern natürlich den Zähler.
- Es gibt Objekte, wie beispielsweise Zeichenkettenkonstanten oder Singletons, die das Referenzenzählen nur vortäuschen. Der Referenzenzähler hat in diesem Fall immer den gleichen, sehr hohen Wert.
- Der lesende Zugriff auf eine atomare Property erhöht den Zählerwert, obwohl die Anzahl der retain- und release-Aufrufe auf dem Objekt ausgeglichen bleibt.
- Es gibt wesentlich effizientere Möglichkeiten, wie Sie die Speicherverwaltung Ihrer App überprüfen können. Halten Sie sich lieber an die oben aufgeführten Speicherverwaltungsregeln. Zur Belohnung werden Ihnen der Objective-C-Compiler, der Debugger und das Programm Instruments viel effektiver helfen, Fehler in der Speicherverwaltung zu entdecken.
Da nicht oft genug darauf hingewiesen werden kann, folgt hier noch ein wichtiger Tipp.
Tipp
Lernen Sie die Speicherverwaltungsregeln. Wenn Sie diese richtig anwenden können, haben Sie eine große Hürde in der Cocoa-Programmierung übersprungen. Sie erleichtern sich die Speicherverwaltung erheblich, wenn Sie konsequent Accessor- statt direkter Attributzugriffe verwenden – am besten über synthetisierte Propertys.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.










Jetzt bestellen





