2.4 Automatisches Referenzenzählen
Die LLVM-Compiler ab Version 3.0 stellen mit dem Automatic Reference Counting (ARC) eine Erweiterung des bestehenden Speicherverwaltungssystems zur Verfügung. Auch wenn es erst mit Xcode 4.2 und iOS 5 zum Standardumfang des SDKs gehört, soll es laut Aussage von Apple bereits unter iOS 4.0 bei Benutzung geeigneter Compiler funktionieren. Das automatische Referenzenzählen ist, bis auf eine Ausnahme, keine Erweiterung des Laufzeitsystems, sondern eine reine Compiler-Erweiterung.
Die Idee, die dahinter steht, ist einfach und brillant. Die Speicherverwaltungsregeln sind einfach und maschinell überprüfbar. Mit dem Analyzer steht Ihnen sogar ein Tool zur Verfügung, mit dem Sie eine Menge Speicherverwaltungsfehler automatisiert entdecken können. Die Idee von ARC ist, die Verfahren des Analyzers zu verwenden, um die Speicherverwaltungsanweisungen mit der Übersetzung automatisch in den Code einzufügen. Laut Apple sorgt der Compiler mit ARC indes nicht nur für eine korrekte Speicherverwaltung, sondern auch für schnelleren Code.
War alles umsonst?
Sie haben mit dem automatischen Referenzenzählen eine starke Vereinfachung in der Speicherverwaltung, die Ihnen die Programmierung erheblich erleichtert. Es ist hingegen keine neue Speicherverwaltung, da der Compiler Ihren Code um die notwendigen Speicherverwaltungsanweisungen erweitert. Für eine sinnvolle Verwendung der ARC-Erweiterung ist es also nach wie vor unerlässlich, dass Sie verstehen, wie die Speicherverwaltung funktioniert.
2.4.1 Zurück in die Zukunft

Wie bereits erwähnt wurde, analysiert beim automatischen Referenzenzählen der Compiler den Programmcode und ergänzt ihn um die notwendigen Anweisungen, damit bei der Ausführung des Programms weder Speicherlecks noch Dangling Pointer entstehen. Da ja mehrere Köche bekanntlich den Brei verderben, bedeutet das im Umkehrschluss für Sie, dass auch Sie Ihre Finger von der Speicherverwaltung zu lassen haben. Sie dürfen die Methoden retain, release und autorelease weder aufrufen noch in Ihren Klassen überschreiben. Außerdem ist das Anlegen und Verwenden von Objekten der Klasse NSAutoreleasePool strengstens untersagt. Zuwiderhandlungen werden mit Übersetzungsfehlern geahndet.
Eine ARC-konforme Methode könnte also so aussehen:
- (NSString *)newName {
return [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
}
Der ARC-Compiler bemerkt den Verstoß gegen die erste Speicherverwaltungsregel und korrigiert den Code während der Übersetzung, indem er ein retain einfügt:
- (NSString *)newName {
return [[NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName] retain];
}
Bei Methoden, deren Name nicht zur ersten Speicherverwaltungsregel passt, sieht die Korrektur etwas komplizierter aus. Aus
- (NSNumber *)age {
return age;
}
erzeugt der ARC-Compiler Folgendes (die Änderungen sind hervorgehoben):
- (NSString *)age {
NSNumber *theAge = [age retain];
return [theAge autorelease];
}
Der Compiler sendet hier also ein retain an den Ergebniswert nach dessen Erzeugung – und beim Verlassen der Methode ein autorelease. Der Compiler verhält sich hier wie ein synthetischer Getter einer atomaren Property. Der ARC-Compiler achtet natürlich auch bei Methodenaufrufen auf die richtige Speicherverwaltung.
Er wandelt
NSLog(@"name = %@", [thePerson newName]);
in
NSString *theName = [thePerson newName];
NSLog(@"name = %@", theName);
[theName release];
um und verwendet hier keinen Autorelease-Pool, sondern eine direkte Freigabe über release.
2.4.2 Weakie und die starken Zeiger

Das sieht ja bis jetzt alles noch recht einfach aus. Bei Variablen ist es jedoch etwas komplizierter. Wenn Sie eine Variable oder ein Attribut deklarieren, setzt der Compiler es auf nil. Außerdem verhält sich die Variable immer wie eine haltende Referenz, die der generierte Code am Ende ihres Gültigkeitsbereiches (Blocks) wieder freigibt. Die Deklaration
{
NSString *theName;
...
}
wandelt der Compiler bei der Übersetzung in
{
__strong NSString *theName = nil;
...
[theName release];
}
um. Bei Attributen erzeugt der Compiler die release-Anweisung natürlich nicht im Block, sondern im dealloc der Klasse.
Der Speicherverwaltungstyp __strong legt dabei fest, dass die Variable den referenzierten Wert hält. Die Initialisierung mit nil erhöht die Stabilität Ihres Codes, da Sie dadurch eine Nachricht an die Objektreferenz senden können, ohne sie explizit vorher zu initialisieren. Die Zuweisung ist außerdem für das korrekte Funktionieren von weiteren Zuweisungen notwendig. Denn aus
theName = theNewName;
erzeugt der Compiler:
[theNewName retain];
NSString *theOldName = theName;
theName = theNewName;
[theOldName release];
Ohne die Initialisierung auf nil enthielte die letzte Zeile in der Regel einen Dangling Pointer.
Das Gegenstück zu __strong ist __weak. Es legt fest, dass die Variable das Objekt nicht hält. So gesehen verhält es sich wie weak bei Propertys. Diesen Typ müssen Sie natürlich immer explizit angeben. Der Compiler erweitert auch hierbei Ihren Programmcode. Das Laufzeitsystem setzt solche schwachen Referenzen automatisch auf nil, wenn das Programm das Objekt löscht, auf das die schwache Referenz zeigt. Dazu verwendet ARC spezielle Funktionen aus der Laufzeitbibliothek. Aus
__weak NSString *theName = name;
NSUInteger theLength = [theName length];
generiert der Compiler:
__weak NSString *theName = nil;
objc_storeWeak(&theName, name);
NSUInteger theLength = [objc_readWeak(&theName) length];
objc_storeWeak(&theName, nil);
Propertys und das automatische Referenzenzählen
Der neue Speicherverwaltungstyp weak verhindert die Entstehung von Dangling Pointern. Wenn Sie ihn anstatt assign verwenden, setzt die Laufzeitumgebung den Property-Wert automatisch auf nil, wenn Ihr Programm das referenzierte Objekt freigibt. Sie können natürlich retain und copy wie gewohnt weiterverwenden. Jedoch ist die Verwendung von strong anstelle von retain bezüglich weak stringenter.
Analog zu den Modifizierern __strong und __weak gibt es die entsprechenden Parameter strong und weak für Propertys. Dabei ist strong nur ein Synonym für retain. Der ARC-Compiler erzeugt automatisch bei Propertys mit den Typen copy, retain und strong die entsprechende Freigabeanweisung in der dealloc-Methode der Klasse. Sie brauchen deswegen also diese Methode nicht zu implementieren.
Der ARC-Compiler sorgt natürlich auch dafür, dass ein Objekt am Ende seines Lebens seine Verweise auf andere Objekte freigibt. Er erzeugt also automatisch eine dealloc-Methode. Sie dürfen jedoch auch weiterhin dealloc überschreiben und für die Freigabe anderer Ressourcenarten (z. B. Filehandles, Registrierungen als Beobachter, Sockets und Streams) verwenden. Der ARC-Compiler fügt am Ende einer selbst geschriebenen dealloc-Methode automatisch den Aufruf der Supermethode ein, und Sie brauchen das nicht mehr zu tun.
Die »dealloc«-Methode und ARC
In einer ARC-Umgebung ist überall der explizite Aufruf von dealloc verboten. Bei Zuwiderhandlung erhalten Sie die Fehlermeldung:
ARC forbids explicit message send of 'dealloc'.
ARC ist eine Sonderform des Referenzenzählens, die Ihnen in vielen Fällen die Speicherverwaltung erheblich vereinfacht. Sie dürfen dabei jedoch weak nicht mit autorelease verwechseln. Im Gegensatz zu einem Objekt im Autorelease-Pool hält eine schwache Referenz niemals das Objekt. Der folgende Code ist zwar zulässig, führt allerdings zu einem Laufzeitfehler:
// Schnittstelle
@property(weak, nonatomic) UILabel *label;
@property(weak, nonatomic) UIButton *button;
// Implementierung
self.label = [[UILabel alloc] initWithFrame:theFrame];
self.button = [UIButton buttonWithType:UIButtonTypeInfoDark];
[self addSubview:self.label];
[self addSubview:self.button];
Listing 2.104 Problematische Verwendung einer weak-Property
Der Compiler übersetzt diesen Code problemlos. Die Property label zeigt bei der Ausführung sofort nach der Zuweisung auf nil. Das liegt daran, dass der ARC-Compiler direkt nach der Erzeugung ein Release an das Label sendet, weil es ja keine haltende Referenz mehr darauf gibt. Dadurch setzt wiederum die Laufzeitumgebung alle schwachen Referenzen auf das Label – also auch die Property – auf nil. Bei dem Button tritt dieses Problem nicht auf, da ihn das Programm über einen Convenience-Konstruktor erzeugt hat, der ihn in den Autorelease-Pool legt. Der Pool hält seinerseits die Referenz, sodass die Laufzeitumgebung die Property button nicht auf nil setzt.
ARC != ARP
Wie Sie an dem Beispiel sehen, setzt automatisches Referenzenzählen nicht die Speicherverwaltungsregeln außer Kraft. Eine Methode, die auf die erste Regel passt, gibt also nach wie vor die Halterschaft am zurückgegebenen Objekt ab, während alle anderen Methoden das Objekt halten (z. B. über den Autorelease-Pool).
Sie können das Problem bei dem Label umgehen, indem Sie eine lokale Variable verwenden:
// Schnittstelle
@property(weak, nonatomic) UILabel *label;
@property(weak, nonatomic) UIButton *button;
// Implementierung
UILabel *theLabel = [[UILabel alloc] initWithFrame:theFrame];
self.label = theLabel;
self.button = [UIButton buttonWithType:UIButtonTypeInfoDark];
[self addSubview:self.label];
[self addSubview:self.button];
Listing 2.105 Unproblematische Verwendung der weak-Property
Diese Variante umgeht das Problem aus Listing 2.104, weil hier die lokale Variable das Label hält. Bei lokalen Variablen erkennt der ARC-Compiler übrigens dieses Problem und warnt Sie. Wenn Sie eine lokale Variable folgendermaßen deklarieren und initialisieren ...
__weak UILabel *theLabel;
theLabel = [[UILabel alloc] initWithFrame:theFrame];
Listing 2.106 Problematische Verwendung einer lokalen Variable
... dann warnt Sie der Compiler mit der Meldung: assigning retained object to weak variable; object will be released after assignment. Warum er das nicht auch bei Propertys macht, bleibt schleierhaft.
Speicherwaltungsregeln und ARC
An dem Beispiel aus Listing 2.104 können Sie sehen, wie wichtig es ist, die Speicherverwaltungsregeln verstanden zu haben – auch bei Verwendung von ARC. Zwar vereinfacht Ihnen ARC die Handhabung der Speicherverwaltung erheblich, allerdings ist es trotzdem wichtig, zu wissen, wie die Speicherverwaltung funktioniert.
Es gibt noch zwei weitere mögliche Speicherverwaltungstypen. Durch den Modifizierer __unsafe_unretained können Sie erreichen, dass sich eine Variable wie ein gewöhnlicher Zeiger in C verhält. Der Compiler verändert den Code für diese Variablen also nicht.
Der Modifizierer __autoreleasing veranlasst den Compiler, ein retain und ein autorelease bei der Zuweisung an den Wert zu senden. Er ist der Standardtyp für Ausgabeparameter – also Zeiger auf Objektreferenzen. Ein typisches Beispiel dafür sind Fehlerobjekte, die viele Cocoa-Methoden zurückgeben:
- (void)readWithError:(NSError **)outError {
...
if(!success) {
*outError = ...;
}
}
Das übersetzt der Compiler in:
- (void)readWithError:(__autoreleasing NSError **)outError {
...
if(!success) {
*outError = [[... retain] autorelease];
}
}
Automatisches Referenzenzählen und ältere iOS-Versionen
Sie dürfen das automatische Referenzenzählen auch verwenden, wenn Ihre App unter iOS 4 laufen soll. Allerdings unterstützt diese iOS-Version keine schwachen Referenzen; Sie dürfen dort also weder weak noch __weak verwenden, da die Laufzeitumgebung die entsprechenden Funktionen (z. B. objc_storeWeak) nicht kennt. Diese Referenzen müssen Sie über assign oder __unsafe_unretained abbilden.
2.4.3 Autorelease-Pools

Wie eingangs erwähnt wurde, dürfen Sie bei der Verwendung des automatischen Referenzenzählens keine Autorelease-Pools über die Klasse NSAutoreleasePool mehr anlegen. In der Regel müssen Sie das in Objective-C sowieso nur selten machen. Die Verwaltung von Autorelease-Pools geschieht in der ARC-Umgebung durch die Direktive @autoreleasepool. Wie schon gesagt wurde, dürfen Sie diese Direktive auch in Programmen für ältere iOS-Versionen verwenden. Sie sollten also generell die Verwaltung über die Klasse vermeiden.
2.4.4 Einzelgänger

Die Richtlinien für ARC-konformen Programmcode untersagen Ihnen nicht nur die Verwendung der Methoden retain, release und autorelease, sondern auch das Überschreiben dieser Methoden. Das mussten Sie bei älteren Versionen in der Regel sowieso selten machen. Für die Implementierung des Singleton-Entwurfsmusters beim manuellen Referenzenzählen überschreibt man in der Regel diese Methoden, um sicherzustellen, dass es nur ein Objekt der Klasse gibt. [E. Gamma, R. Helm, R. Johnson, J. Vlissides: Entwurfsmuster. Elemente wiederverwendbarer objektorientierter Software, München: Addison-Wesley 2004.]
Pattern oder Antipattern?
Singletons gehören zu den 23 Entwurfsmustern, die Gamma, Helm, Johnson und Vlissides – die berühmte Gang of Four – in ihrem Buch über Entwurfsmuster14 beschrieben haben. Allerdings betrachtet inzwischen auch eine große Anzahl von Entwicklern das Singleton-Muster als Antipattern, weil es leicht zu statischem und unflexiblem Programmcode führen kann. Auch Erich Gamma sieht das Muster inzwischen kritisch:
»When discussing which patterns to drop, we found that we still love them all. (Not really—I'm in favor of dropping Singleton. Its use is almost always a design smell.)«
Wie dem auch sei, setzen Sie dieses Muster allenfalls in homöopathischen Dosen ein. Außerdem sollten Sie bei Ihren Singletons auch immer an die Einschränkungen durch das MVC-Architekturmuster denken. In der Regel gehört es zur Controller-Schicht und hat in der View- oder gar Modellschicht nichts verloren.
Wir stellen hier eine einfache Möglichkeit vor, wie Sie Singletons in ARC-Umgebungen implementieren können, obwohl wir der Ansicht von Erich Gamma zustimmen, dass sie fast immer ein Design Smell sind – also ein schlechtes Design. Andererseits muss jeder Programmierer selbst entscheiden, welche Muster und welches Design er bevorzugt.
Die einfachste Möglichkeit für die Implementierung eines Singletons ist eine Klassenmethode und eine statische Variable. Die statische Variable verweist dabei auf das Objekt und hält es im Speicher.
@implementation MySingleton
+ (id)sharedInstance {
static id sharedInstance;
if (sharedInstance == nil) {
sharedInstance = [[MySingleton alloc] init];
}
return sharedInstance;
}
...
@end
Listing 2.107 Einfaches Singleton, über eine Klassenmethode implementiert
2.4.5 Migration bestehender Projekte
Das Verbot der drei Speicherverwaltungsmethoden retain, release und autorelease führt natürlich zu Programmcode, der ohne ARC eine fehlerhafte Speicherverwaltung hat. Da der Compiler nach wie vor auch die manuelle Speicherverwaltung unterstützt, müssen Sie ihm mitteilen, um welche Art von Code es sich handelt. Dazu gibt es das Kommandozeilenflag –fobjc-arc beziehungsweise die Einstellung Objective-C Automatic Reference Counting in den Build Settings des Programm-Targets (siehe Abbildung 2.42).
Abbildung 2.42 Automatisches Referenzenzählen einschalten
Sie sollten allerdings diese Einstellung nicht einfach ändern. Um Ihren bestehenden Code für das automatische Referenzenzählen umzuwandeln, können Sie stattdessen ab Xcode 4.2 den Menüpunkt Edit • Refactor • Convert to Objective-C ARC... aufrufen. Stellen Sie jedoch vorher das Schema auf den iPhone-Simulator um (siehe Abbildung 2.43). Dadurch können Sie einen Fehler bei der Migration verhindern.
Abbildung 2.43 Umstellen des Schemas auf den iPhone-Simulator
Nach dem Aufruf des Menüpunkts fragt Xcode Sie, welches Target Ihres Projekts Sie konvertieren möchten (siehe Abbildung 2.44). Die bislang erstellten Projekte enthalten jeweils nur ein Target, und es macht bei Projekten mit mehreren Targets nur in seltenen Fällen Sinn, gemischten Code zu haben. Die Auswahl fällt also in der Regel leicht. Des Weiteren können Sie die Konvertierung für einzelne Dateien ein- oder ausschalten, indem Sie das Dreieck neben dem Häkchen anklicken.
Abbildung 2.44 Auswahl des Konvertierungstargets
Wenn Sie Precheck anklicken, startet Xcode eine Prüfung für die Konvertierung. Xcode 4.2 konnte noch keine Autorelease-Pools migrieren und meldete drei Fehler in der main-Funktion (siehe Abbildung 2.45). Sie können diesen Fehler beheben, indem Sie ihn durch den Code aus Listing 2.89 ersetzen. Wenn Sie alle gemeldeten Fehler behoben haben, starten Sie die Konvertierung einfach erneut.
Abbildung 2.45 Konvertierungsfehler in der »main«-Funktion
Wenn Xcode dabei keine Fehler mehr findet, zeigt es einen Dialog an, der Ihnen die nachfolgenden Schritte erläutert (siehe Abbildung 2.46). Klicken Sie einfach auf den Button Next, um fortzufahren.
Abbildung 2.46 Start der Konvertierung
Anschließend zeigt Xcode einen Fortschrittsdialog für die Konvertierung an. Falls Sie für das Projekt noch keine Snapshots eingeschaltet haben, fragt Xcode Sie danach.
Bitte recht freundlich
Ein Snapshot ist eine Momentaufnahme Ihres Projekts. Er enthält alle Dateien und Einstellungen des Projekts von dem Zeitpunkt, an dem Xcode ihn erstellt hat. Sie können damit also Zwischenstände Ihrer Projekte speichern. Das ist besonders praktisch bei größeren Änderungen an Ihrem Programmcode, besonders wenn Xcode sie automatisiert durchführt.
Über den Menüpunkt File • Create Snapshot... lassen sich jederzeit auch eigene Zwischenstände speichern. Um einen Snapshot wiederherzustellen, verwenden Sie einfach den Menüpunkt File • Restore Snapshot...
Da Xcode bei größeren Projekten Ihren Code sehr stark verändert, sollten Sie auf jeden Fall Snapshots zulassen (siehe Abbildung 2.47).
Abbildung 2.47 Einschalten der Snapshots
Anschließend startet Xcode die Konvertierung Ihres Projekts. Es zeigt dabei den Fortschritt in einem Dialog an. Nach der Konvertierung zeigt Xcode ein Fenster mit den Änderungen an den einzelnen Dateien an (siehe Abbildung 2.48). Sie können in der linken Spalte die Datei auswählen, deren Änderungen Sie ansehen möchten. Daneben sehen Sie den alten Quellcode der ausgewählten Datei und auf der rechten Seite den geänderten Code. Die Ansicht hinterlegt die Änderungen dabei in Hellblau.
Abbildung 2.48 Anzeige der Änderungen
Wenn Sie schließlich auf den Button Accept Changes klicken, übernimmt Xcode alle Änderungen in Ihren Programmcode und passt auch die Compiler-Einstellungen in den Build-Settings an. Nach diesem letzten Schritt haben Sie die Migration auf das automatische Referenzenzählen abgeschlossen.
Ja, ich will
Auch wenn das automatische Referenzenzählen der Standard für alle zukünftigen Entwicklungen und Projekte ist, müssen Sie Ihre Projekte nicht unbedingt konvertieren. Sie können auch Code mit und ohne ARC innerhalb einer App verwenden, wenn Sie die Programmteile mit jeweils den richtigen ARC-Einstellungen übersetzt haben. Das ist möglich, weil ja ARC auf dem manuellen Referenzenzählen basiert. Falls Ihnen Xcode bei der Konvertierung zu viele Fehler meldet, können Sie die Umstellung also ruhig noch etwas auf die lange Bank schieben. Mit Xcode 4.5 ist die Konvertierung allerdings im Vergleich zu den Vorgängerversionen schon sehr stabil.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.











Jetzt bestellen





