Retain, Release und Autorelease

Retain, Release und Autorelease
Die Speicherverwaltung von Objekten ist ein großes Thema. Zunächst muss man verstehen, dass man eigentlich nur mit Zeigern, also Verknüpfungen auf Objekte arbeitet, niemals mit den Objekten selbst. Weist man also einem Objekt ein anderes Objekt per Zuweisungsoperator zu (z.B. string2 = string1), dann wird in Wirklichkeit nur der Zeiger kopiert. Beide Variablen verweisen auf das gleiche Objekt.
Das hat nachhaltige Folgen. Schickt man Methoden an das Originalobjekt, dann wirkt sich das genauso auf den zweiten "Zeiger" aus und umgekehrt, genau wie bei einer Datei auf der Festplatte und einem Alias (unter Windows auch 'Verknüpfung' genannt).
Was passiert also, wenn ein Programmteil ein Objekt per release freigibt (siehe Artikel Objekte erstellen) und ein anderer Programmteil greift nun doch nochmal über einen anderen Zeiger auf das Objekt zu? Das Programm stürzt gnadenlos ab!
Um dieses Problem zu umgehen, hat man in Cocoa die Möglichkeit geschaffen einem Objekt zu sagen, dass man es noch benötigt. Gibt dann ein anderer Programmteil das Objekt per release frei, wird es nicht wirklich freigegeben. Es wird erst freigegeben, wenn alle beteiligten Programmteile per release bestätigt haben, dass sie das Objekt nicht mehr benötigen. Die Objekte zählen intern mit, wie oft sie benötigt werden. Den "Anspruch" auf ein Objekt meldet man mit retain an:
NSString* string2; // Einen neuen Zeiger definieren
string2 = string1; // Eine Kopie des Zeigers 'string1' anfertigen
[string2 retain];  // Dem System mitteilen, dass wir 'string2' eine Weile benötigen

[mach_was_sinnvolles];

[string2 release]; // wir geben das Objekt wieder frei

Nochmals zur Verdeutlichung: Man kann das retain oder release oben auch an string1 schicken. Es handelt sich ja um das gleiche Objekt, nur unter einem anderen Namen!
Wie man sicher schnell erkennt, macht ein retain auf ein Objekt meistens nur Sinn, wenn es über mehrere Methoden hinweg erhalten bleiben soll. Innerhalb eines einzelnen Codeblocks kann ich ja schließlich auch den ersten Zeiger benutzen. Hierzu seht ihr in unserem Videocast mehr! Wir behandeln das Thema Speicherverwaltung ausführlich ab der Folge '#007: Speicher, Zeiger und Objekte'.

Alle Methoden, die mit init oder copy beginnen, machen übrigens automatisch ein retain. Hier ist also zum Ende der Benutzung nur noch ein release notwendig.
Auch wenn man ein Objekt als Parameter an ein anderes Objekt übergibt, dann muss sich der "Empfänger" selbst darum kümmern, ob er das Objekt noch eine Weile braucht, zum Beispiel wenn man einen String an ein NSTextField übergibt. Man muss also nicht für andere Objekte mitdenken:
[meinTextFeld setStringValue:string1];
// Hier muss man nicht für das Textfeld mitdenken.
// Das Textfeld macht selbst ein retain auf string1, wenn es den String behalten will.

Weiss man zu Beginn einer Codeblocks bereits, dass man ein selbsterstelltes Objekt nur kurzfristig benötigt, dann kann man auch die Funktion autorelease benutzen und es damit dem System überlassen, dass dem Objekt irgendwann nach Beendigung des aktuellen Codeblocks ein release geschickt wird:
NSString* string1 = [[[NSString alloc] init] autorelease];

Manchmal ist es erwünscht, dass man doch eine komplett neue Kopie eines Objektes erhält, die somit auch nicht mehr mit dem Originalobjekt in Verbindung steht. Hierzu gibt es die Methode copy. Diese Methode kopiert eimal das komplette Objekt in einen neuen Speicherbereich und führt auch schon ein retain aus:
NSString* string1 = @"Hallo Welt!";
NSString* string2 = [string1 copy];
// Erzeugt eine Kopie von string1. Es gibt also nun zwei Objekte.
// Entsprechend wird auch doppelt so viel Speicherplatz verbraucht.

Mit retain und release muss man sehr gut aufpassen! Ein retain zuviel, schon wird der Speicherbereich nicht mehr freigegeben. Ein release zuviel, schon stürzt das Programm ab!
12 Kommentare | Permalink | Trackback-Info

Anzeige / Partnerlink

Kommentar hinzufügen

 
Name:
Email (optional):
Text:
Sicherheitscode:
Bitte geben Sie hier die unten abgebildete 5-stellige Zahl aus dem Bild ein!
Um die 1 besser von der 7 unterscheiden zu können, hat die 1 unten immer einen waagerechten Strich.

12. Sven am 28. Jun 2010, 07:29 Uhr

Hallo!

Vielen Dank für die Ganze Mühe! Weiter so :P

Wo ist der Unterschied zwischen autorealease und sich garnicht um Release und retain kümmern?

11. Ralph Bergmann am 6. Apr 2010, 14:48 Uhr

Hat sich da nicht ein kleiner Fehler eingeschlichen?

Du schreibst immer, dass init den retainCount um 1 erhöhen. Doch dies ist doch garnicht so.

Laut http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html erhöhen alloc und copy, nicht aber init.

Ralph

10. ingo am 24. Jan 2010, 20:21 Uhr

@Witold:

Es gibt eine einfache Regel: man muss seine Objekte immer selbst retainen und entsprechend auch wieder releasen, außer bei init- oder copy-Methoden.
wenn Du Dir also eine Variable über einen längeren Zeitraum bunkern willst, in Deiner eigenen Instanzvariablen, dann musst Du auch retainen.

In Deinem Fall merkst Du Dir das Objekt ja nur kurz in einer Variablen innerhalb Deiner Methode. Da muss man dann auch nicht retainen, weil die Variable ja sowieso mit Beendigung der Methode futsch ist. Das kann höchstens Probleme machen, wenn mehrere parallele Threads mit der Variablen arbeiten wollen.

Mit addObject wiederum übergibst Du das Objekt einem anderen Objekt, in diesem Falle ein Array. Wenn ein solches Array meint, dass irgendein retain oder release nötig ist, dann hat es das gefälligst selbst zu tun. Ein Objekt ist also immer für seine eigenen Variablen verantwortlich.

9. Witold am 24. Jan 2010, 11:58 Uhr

Hallo,

vielen Dank für den guten Artikel.

Ich habe eine kurze Frage, die ich nicht verstehe:

Folgender Code:

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

NSMutableString *s = [[NSMutableString alloc] initWithString: @"test"];

// hier ist der retain count von s 1

[a addObject: s];

// hier ist der retain count von s 2 (pointer in s und im Array a)


[s release];

NSMutableString *s2 = [a lastObject];

Wenn ich mir den Retain-Cound von s2 ausgebe, steht da 1.
Müsste er nicht 2 sein, weil sowohl s2 wie auch das Array a drauf zeigen?
Woran liegt das?

Vielen Dank für einen Hinweis!

Gruß, Witold

8. ingo am 19. Oct 2009, 23:43 Uhr

@Jörg:
Richtig. @"..." ist ein Makro und erstellt zur Kompilerzeit ein Stringobjekt. Man kann alle Methoden an einen solchen String schicken, die man auch an einen NSString schicken kann. Allerdings wird ein solcher Makrostring niemals durch ein release freigegeben!

7. Jörg am 19. Oct 2009, 23:22 Uhr

Hallo,

jetzt mal eine (vielleicht) blöde Frage, was ist der Unterschied zwischen:
NSString* string1 = @"Hallo Welt!";

und

NSString* string1 = [[NSString alloc] initWithString:@"Hallo Welt!"];

Wird bei der ersten Variante explizit ein alloc gemacht? Weil wie es bisher gesehen habe muss bei der ersten kein release gemacht werden und der Zugriff darauf hat genauso funktioniert. Diese eine Sache fehlt mir noch um es ganz zu verstehen.

Danke!

6. Paul am 23. Sep 2009, 15:03 Uhr

Super,
danke. So hatte ich mir das schon fast gedacht. Dann habe ich das jetzt auch verstanden.

Vielen Dank und macht weiter so!

5. ingo am 23. Sep 2009, 15:00 Uhr

Hi Paul,
string1 und string2 sind ja derselbe String, soweit ist es ja klar. Als string1 ist er ja höchswahrscheinlich auch irgendwie per retain reserviert worden, also muss ein weiteres release abgeschickt werden. Beispiel:

NSString* string1 = [[NSString alloc] initWithString:@"Hallo Welt!"]; // der retainCount ist nun 1, dank dem "init"
NSString* string2 = string1; // der retainCount ist weiterhin 1
[string2 retain]; // der retainCount ist nun 2
[string1 release]; // der retainCount ist wieder 1
[string2 release]; // der retainCount geht auf 0, der Speicher wird vom System freigegeben!

Hätte man in der ersten Zeile noch ein autorelease hinzugefügt, dann dürfte man string1 NICHT releasen, da dies irgendwann durch das System passiert!

4. Paul am 23. Sep 2009, 14:37 Uhr

Hallo Leute,
ich habe noch eine Frage zu diesem Beispiel:
NSString* string2; // Einen neuen Zeiger definieren
string2 = string1; // Eine Kopie des Zeigers 'string1' anfertigen
[string2 retain]; // Dem System mitteilen, dass wir 'string2' eine Weile benötigen

[mach_was_sinnvolles];

[string2 release]; // wir geben das Objekt wieder frei

Wenn ich den kompletten Speicher wieder freigeben will muss ich dann noch ein [string 1 release] machen oder ist dieser automatisch freigegeben, weil ich den Zeiger mit string2 erstellt habe??

Danke!

Paul

3. Mathias (Speedy) am 15. Sep 2009, 22:48 Uhr

Aha, also gibt's sowas wie einen Garbage Collector auch bei Objective-C - hab' ich gar nicht gewusst.

Aber wenn's das beim iPhone nicht gibt, bin ich froh, dass ich's jetzt dank Ingo & Peter lerne ;) - danke

2. peter am 15. Sep 2009, 07:44 Uhr

Hallo Sebastian,

der Einstieg wäre evtl. einfacher, der Kopfschmerz danach umso heftiger. Denn das iPhone unterstützt keinen GC und spätestens dann müsste man doch mit dem Speicherkram befassen.

Peter

1. Sebastian R. am 15. Sep 2009, 07:02 Uhr

Ich würde mich freuen, wenn ihr bei Gelegenheit mal was zum Cocoa Garbage Collection Framework sagen könntet. Ist der GC nicht - gerade für Cocoa-Einsteiger, eine gute Alternative zum manuellen Speichermanagement (was natürlich nicht heißen soll, dass das Hintergrundwissen über Speichermanagement unwichtig ist)? Gerade alle, die aus der Java-Ecke kommen, würden sich über GC in Cocoa sicher freuen ;-)