Grundlegendes zur Referenzzählung mit Cocoa und Objective-C


122

Ich fange gerade an, mir Objective-C und Cocoa anzuschauen, um mit dem iPhone SDK zu spielen. Ich bin ziemlich zufrieden mit Cs mallocund dem freeKonzept, aber das Referenzzählschema von Cocoa hat mich ziemlich verwirrt. Mir wurde gesagt, dass es sehr elegant ist, wenn Sie es erst einmal verstanden haben, aber ich bin noch nicht über den Berg.

Wie tun release, retainund autoreleaseArbeit und was sind die Konventionen über ihre Verwendung?

(Oder was hast du gelesen, was dir geholfen hat, es zu bekommen?)

Antworten:


148

Beginnen wir mit retainund release; autoreleaseist wirklich nur ein Sonderfall, wenn Sie die Grundkonzepte verstanden haben.

In Cocoa verfolgt jedes Objekt, wie oft auf es verwiesen wird (insbesondere NSObjectimplementiert die Basisklasse dies). Wenn Sie retainein Objekt aufrufen , teilen Sie ihm mit, dass Sie seine Referenzanzahl um eins erhöhen möchten. Durch Aufrufen releaseteilen Sie dem Objekt mit, dass Sie es loslassen, und sein Referenzzähler wird dekrementiert. Wenn nach dem Aufruf releaseder Referenzzähler jetzt Null ist, wird der Speicher dieses Objekts vom System freigegeben.

Der einfachste Weg , dies unterscheidet sich von mallocund freeist , dass eine bestimmte Aufgabe nicht über andere Teile des Systems zu sorgen braucht abstürzt , weil Sie Speicher freigegeben haben sie verwendet haben . Angenommen, jeder spielt mit und behält / gibt gemäß den Regeln frei. Wenn ein Code das Objekt behält und dann freigibt, bleibt jeder andere Code, der ebenfalls auf das Objekt verweist, unberührt.

Was manchmal verwirrend sein kann, ist zu wissen, unter welchen Umständen Sie anrufen sollten retainund release. Meine allgemeine Faustregel lautet: Wenn ich längere Zeit an einem Objekt festhalten möchte (z. B. wenn es sich um eine Mitgliedsvariable in einer Klasse handelt), muss ich sicherstellen, dass die Referenzanzahl des Objekts über mich Bescheid weiß. Wie oben beschrieben, wird der Referenzzähler eines Objekts durch Aufrufen erhöht retain. Konventionell wird es auch erhöht (wirklich auf 1 gesetzt), wenn das Objekt mit einer "init" -Methode erstellt wird. In beiden Fällen liegt es in meiner Verantwortung, releasedas Objekt aufzurufen, wenn ich damit fertig bin. Wenn ich das nicht tue, wird es einen Speicherverlust geben.

Beispiel für die Objekterstellung:

NSString* s = [[NSString alloc] init];  // Ref count is 1
[s retain];                             // Ref count is 2 - silly
                                        //   to do this after init
[s release];                            // Ref count is back to 1
[s release];                            // Ref count is 0, object is freed

Nun zu autorelease. Die Autorelease wird als bequeme (und manchmal notwendige) Methode verwendet, um das System anzuweisen, dieses Objekt nach einer Weile freizugeben. Aus Sanitärsicht autoreleasewird der aktuelle Thread NSAutoreleasePoolbeim Aufruf über den Anruf informiert. Das NSAutoreleasePoolweiß jetzt, dass es releasedas Objekt aufrufen kann, sobald es eine Gelegenheit erhält (nach der aktuellen Iteration der Ereignisschleife) . Aus unserer Sicht als Programmierer kümmert es sich darum, releaseuns anzurufen, also müssen wir nicht (und sollten es auch nicht).

Was Anmerkung Wichtig ist , dass (wieder durch Konvention) alle Objekterstellung Klasse Methoden geben ein Autoreleased Objekt. Im folgenden Beispiel hat die Variable "s" beispielsweise einen Referenzzähler von 1, der jedoch nach Abschluss der Ereignisschleife zerstört wird.

NSString* s = [NSString stringWithString:@"Hello World"];

Wenn Sie an dieser Zeichenfolge festhalten möchten, müssen Sie sie retainexplizit aufrufen und dann explizit release, wenn Sie fertig sind.

Betrachten Sie den folgenden (sehr erfundenen) Code, und Sie werden eine Situation sehen, in der dies autoreleaseerforderlich ist:

- (NSString*)createHelloWorldString
{
    NSString* s = [[NSString alloc] initWithString:@"Hello World"];

    // Now what?  We want to return s, but we've upped its reference count.
    // The caller shouldn't be responsible for releasing it, since we're the
    // ones that created it.  If we call release, however, the reference 
    // count will hit zero and bad memory will be returned to the caller.  
    // The answer is to call autorelease before returning the string.  By 
    // explicitly calling autorelease, we pass the responsibility for
    // releasing the string on to the thread's NSAutoreleasePool, which will
    // happen at some later time.  The consequence is that the returned string 
    // will still be valid for the caller of this function.
    return [s autorelease];
}

Mir ist klar, dass dies alles etwas verwirrend ist - irgendwann wird es jedoch klicken. Hier sind einige Referenzen, die Sie zum Laufen bringen:

  • Apples Einführung in die Speicherverwaltung.
  • Cocoa Programming für Mac OS X (4. Ausgabe) von Aaron Hillegas - ein sehr gut geschriebenes Buch mit vielen großartigen Beispielen. Es liest sich wie ein Tutorial.
  • Wenn Sie wirklich eintauchen, können Sie zur Big Nerd Ranch gehen . Dies ist eine Schulungseinrichtung, die von Aaron Hillegas - dem Autor des oben genannten Buches - betrieben wird. Ich habe dort vor einigen Jahren den Intro to Cocoa-Kurs besucht und es war eine großartige Möglichkeit zu lernen.

8
Sie haben geschrieben: "Durch Aufrufen von Autorelease erhöhen wir vorübergehend die Referenzanzahl". Ich denke das ist falsch; Autorelease markiert nur das Objekt, das in Zukunft veröffentlicht werden soll. Es erhöht nicht die Ref-Anzahl: cocoadev.com/index.pl?AutoRelease
LKM

2
"Jetzt zur Autorelease. Die Autorelease wird als bequeme (und manchmal notwendige) Methode verwendet, um das System anzuweisen, dieses Objekt nach einer Weile freizugeben." Als Einstiegssatz ist dies falsch. Es sagt dem System nicht, dass es "freigeben" soll, sondern dass es die Anzahl der Aufbewahrungen verringern soll.
Mmalc

3
Vielen Dank für die gute Erklärung. Nur eine Sache, die noch unklar ist. Wenn NSString* s = [[NSString alloc] initWithString:@"Hello World"];ein automatisch freigegebenes Objekt zurückgegeben wird (während Sie es schreiben), warum muss ich dann ein return [s autorelease];"autorelease" erneut einstellen und nicht nur return s?
ZnQ

3
@Stefan: Gibt [[NSString alloc] initWithString:@"Hello World"]KEIN automatisch freigegebenes Objekt zurück. Bei jedem allocAufruf wird der Referenzzähler auf 1 gesetzt, und es liegt in der Verantwortung dieses Codes, sicherzustellen, dass er freigegeben wird. Der [NSString stringWithString:]Aufruf, auf der anderen Seite, nicht zurückkehrt ein Autoreleased Objekt.
Matt Dillard

6
Wissenswertes: Da die Antwort @ "" und NSString verwendet, sind die Zeichenfolgen durchgehend konstant, und daher ist die absolute Anzahl der Beibehaltungen sowohl konstant als auch völlig irrelevant. Die Antwort ist keineswegs falsch verstärkt die Tatsache, dass absolute Retentionszahlen nie wirklich etwas sind, worüber Sie sich Sorgen machen sollten.
bbum

10

Wenn Sie den Prozess der Aufbewahrung / Freigabe verstehen, gibt es zwei goldene Regeln, die für etablierte Cocoa-Programmierer "duh" offensichtlich sind, aber für Neulinge leider selten klar formuliert werden.

  1. Wenn eine Funktion, die ein Objekt zurückgibt alloc, createoder copyin ihrem Namen vorhanden ist, gehört das Objekt Ihnen. Sie müssen anrufen, [object release]wenn Sie damit fertig sind. Oder CFRelease(object)wenn es sich um ein Core-Foundation-Objekt handelt.

  2. Wenn der Name keines dieser Wörter enthält, gehört das Objekt einer anderen Person. Sie müssen aufrufen, [object retain]wenn Sie das Objekt nach dem Ende Ihrer Funktion behalten möchten.

Es wäre gut, wenn Sie diese Konvention auch in Funktionen befolgen würden, die Sie selbst erstellen.

(Nitpickers: Ja, es gibt leider einige API-Aufrufe, die Ausnahmen von diesen Regeln darstellen, aber selten sind.)


11
Dies ist unvollständig und ungenau. Ich verstehe weiterhin nicht, warum Leute versuchen, die Regeln zu wiederholen, anstatt einfach auf die entsprechende Dokumentation zu verweisen: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
mmalc

4
Insbesondere die Regeln der Core Foundation unterscheiden sich von denen von Cocoa. siehe developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc

1
Ich bin auch anderer Meinung. Wenn eine Funktion etwas zurückgibt, das sie nicht besitzen möchte, sollte sie es automatisch freigeben. Es ist der Aufrufer des Funktionsjobs, diesen beizubehalten (falls gewünscht). Es sollte NICHTS mit dem Namen einer aufgerufenen Methode zu tun haben. Das ist mehr C-Style-Codierung, bei der der Besitz von Objekten unklar ist.
Sam

1
Es tut uns leid! Ich glaube, ich hatte es eilig, abzustimmen. Speicherverwaltungsregeln Ihre Antwort zitiert fast das Apple-Dokument.
Sam

8

Wenn Sie Code für den Desktop schreiben und auf Mac OS X 10.5 abzielen können, sollten Sie zumindest die Verwendung der Objective-C-Garbage Collection in Betracht ziehen. Dies wird den größten Teil Ihrer Entwicklung wirklich vereinfachen. Deshalb hat Apple alle Anstrengungen unternommen, um es überhaupt erst zu erstellen und eine gute Leistung zu erzielen.

Wie für die Speicherverwaltungsregeln, wenn GC nicht verwendet wird:

  • Wenn Sie ein neues Objekt erstellen , mit +alloc/+allocWithZone:, +new, -copyoder , -mutableCopyoder wenn Sie -retainein Objekt, nehmen Sie den Besitz davon und muss dafür sorgen , es gesendet wird -release.
  • Wenn Sie ein Objekt auf andere Weise erhalten, sind Sie nicht dessen Eigentümer und sollten nicht sicherstellen, dass es gesendet wird -release.
  • Wenn Sie sicherstellen möchten, dass ein Objekt gesendet wird -release, können Sie es entweder selbst senden oder Sie können das Objekt senden, -autoreleaseund der aktuelle Autorelease-Pool sendet es -release(einmal pro Empfang -autorelease), wenn der Pool geleert wird.

Typischerweise -autoreleasewird als eine Möglichkeit , um sicherzustellen , dass Objekte verwendet für die Länge des aktuellen Ereignisses leben, sind aber danach gereinigt, da es ein Autofreigabepool ist die Cocoa Ereignisverarbeitung umgibt. In Cocoa ist es weitaus üblicher, Objekte an einen Aufrufer zurückzugeben, die automatisch freigegeben wurden, als Objekte zurückzugeben, die der Aufrufer selbst freigeben muss.


6

Objective-C verwendet die Referenzzählung, dh jedes Objekt hat eine Referenzzählung. Wenn ein Objekt erstellt wird, hat es einen Referenzzähler von "1". Einfach ausgedrückt, wenn auf ein Objekt verwiesen wird (dh irgendwo gespeichert wird), wird es "beibehalten", was bedeutet, dass seine Referenzanzahl um eins erhöht wird. Wenn ein Objekt nicht mehr benötigt wird, wird es "freigegeben", was bedeutet, dass seine Referenzanzahl um eins verringert wird.

Wenn der Referenzzähler eines Objekts 0 ist, wird das Objekt freigegeben. Dies ist die grundlegende Referenzzählung.

Für einige Sprachen werden Referenzen automatisch vergrößert und verkleinert, aber Ziel-c ist keine dieser Sprachen. Somit ist der Programmierer für das Beibehalten und Freigeben verantwortlich.

Eine typische Methode zum Schreiben einer Methode ist:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

Das Problem, dass Sie daran denken müssen, erworbene Ressourcen innerhalb des Codes freizugeben, ist sowohl mühsam als auch fehleranfällig. Objective-C führt ein weiteres Konzept ein, das dies erheblich vereinfachen soll: Autorelease-Pools. Autorelease-Pools sind spezielle Objekte, die auf jedem Thread installiert werden. Sie sind eine ziemlich einfache Klasse, wenn Sie NSAutoreleasePool nachschlagen.

Wenn an ein Objekt eine "Autorelease" -Nachricht gesendet wird, sucht das Objekt nach Autorelease-Pools, die für diesen aktuellen Thread auf dem Stapel liegen. Das Objekt wird der Liste als Objekt hinzugefügt, an das zu einem späteren Zeitpunkt eine Freigabemeldung gesendet werden soll. Dies ist im Allgemeinen der Fall, wenn der Pool selbst freigegeben wird.

Wenn Sie den obigen Code verwenden, können Sie ihn so umschreiben, dass er kürzer und leichter zu lesen ist, indem Sie sagen:

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Da das Objekt automatisch freigegeben wird, müssen wir es nicht mehr explizit als "Freigabe" bezeichnen. Dies liegt daran, dass wir wissen, dass ein Autorelease-Pool dies später für uns erledigt.

Hoffentlich hilft das. Der Wikipedia-Artikel befasst sich ziemlich gut mit Referenzzählung. Weitere Informationen zu Autorelease-Pools finden Sie hier . Beachten Sie außerdem, dass Sie Xcode beim Erstellen für Mac OS X 10.5 und höher anweisen können, mit aktivierter Speicherbereinigung zu erstellen, sodass Sie das Beibehalten / Freigeben / Autorelease vollständig ignorieren können.


2
Das ist einfach falsch. In keinem der gezeigten Beispiele muss eine Objektfreigabe oder Autorlease gesendet werden.
Mmalc

6

Joshua (# 6591) - Die Garbage Collection in Mac OS X 10.5 scheint ziemlich cool zu sein, ist aber für das iPhone nicht verfügbar (oder wenn Sie möchten, dass Ihre App auf Versionen vor 10.5 von Mac OS X ausgeführt wird).

Wenn Sie eine Bibliothek schreiben oder etwas, das möglicherweise wiederverwendet wird, sperrt die Verwendung des GC-Modus jeden, der den Code verwendet, in die Verwendung des GC-Modus. Soweit ich weiß, tendiert jeder, der versucht, weit wiederverwendbaren Code zu schreiben, dazu, diese zu verwalten Speicher manuell.


2
Es ist durchaus möglich, ein Hybrid-Framework zu schreiben, das sowohl GC- als auch Referenzzählung unterstützt.
Mmalc

6

Wie immer, wenn Leute versuchen, das Referenzmaterial neu zu formulieren, verstehen sie fast immer etwas falsch oder liefern eine unvollständige Beschreibung.

Apple bietet eine vollständige Beschreibung des Speicherverwaltungssystems von Cocoa im Programmierhandbuch für die Speicherverwaltung für Cocoa . Am Ende finden Sie eine kurze, aber genaue Zusammenfassung der Speicherverwaltungsregeln .




2
Eigentlich ist dies eine viel bessere einseitige Zusammenfassung: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau

6

Ich werde nicht zu den Besonderheiten der Aufbewahrung / Freigabe hinzufügen, außer dass Sie vielleicht darüber nachdenken möchten, 50 US-Dollar zu verlieren und das Hillegass-Buch zu erhalten, aber ich würde dringend empfehlen, die Tools von Instruments sehr früh in der Entwicklung Ihrer Anwendung zu verwenden (auch Ihre Erster!). Führen Sie dazu Run-> Start with Performance Tools aus. Ich würde mit Leaks beginnen, einem der vielen verfügbaren Instrumente, die Ihnen jedoch zeigen, wenn Sie vergessen haben, sie freizugeben. Es ist ziemlich entmutigend, wie viele Informationen Ihnen präsentiert werden. Schauen Sie sich dieses Tutorial an, um schnell
aufzustehen: COCOA TUTORIAL: BEHEBEN VON SPEICHERLECKEN MIT INSTRUMENTEN

Der Versuch, Lecks zu erzwingen, könnte eine bessere Möglichkeit sein, zu lernen, wie man sie verhindert! Viel Glück ;)


5

Matt Dillard schrieb :

return [[s Autorelease] release];

Autorelease ist nicht behält das Objekt. Autorelease stellt es einfach in die Warteschlange, um es später freizugeben. Sie möchten dort keine Release-Erklärung haben.




4

Die Antwort von NilObject ist ein guter Anfang. Hier finden Sie einige zusätzliche Informationen zur manuellen Speicherverwaltung ( auf dem iPhone erforderlich ).

Wenn Sie persönlich alloc/initein Objekt haben, wird es mit einem Referenzzähler von 1 geliefert. Sie sind dafür verantwortlich, danach aufzuräumen, wenn es nicht mehr benötigt wird, entweder telefonisch [foo release]oder telefonisch [foo autorelease]. Release bereinigt es sofort, während Autorelease das Objekt zum Autorelease-Pool hinzufügt, wodurch es zu einem späteren Zeitpunkt automatisch freigegeben wird.

Autorelease ist in erster Linie für den Fall gedacht, dass Sie eine Methode haben, die das betreffende Objekt zurückgeben muss ( Sie können es also nicht manuell freigeben, andernfalls geben Sie ein Null-Objekt zurück ), aber Sie möchten es auch nicht festhalten .

Wenn Sie ein Objekt erwerben, für das Sie nicht alloc / init aufgerufen haben, um es abzurufen - zum Beispiel:

foo = [NSString stringWithString:@"hello"];

Wenn Sie jedoch an diesem Objekt festhalten möchten, müssen Sie [foo keep] aufrufen. Andernfalls ist es möglich, dass dies der Fall ist autoreleasedund Sie an einer Null-Referenz festhalten (wie im obigen stringWithStringBeispiel ). Wenn Sie es nicht mehr brauchen, rufen Sie an [foo release].


2

Die obigen Antworten geben klare Anpassungen dessen, was in der Dokumentation steht. Das Problem, auf das die meisten neuen Leute stoßen, sind die undokumentierten Fälle. Beispielsweise:

  • Autorelease : Dokumente sagen, dass es "irgendwann in der Zukunft" eine Veröffentlichung auslösen wird. WANN?! Grundsätzlich können Sie sich darauf verlassen, dass sich das Objekt in der Nähe befindet, bis Sie Ihren Code wieder in die Systemereignisschleife zurückgeben. Das System kann das Objekt jederzeit nach dem aktuellen Ereigniszyklus freigeben. (Ich denke, Matt hat das früher gesagt.)

  • Statische Zeichenfolgen : NSString *foo = @"bar";- Müssen Sie diese beibehalten oder freigeben? Wie wäre es

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • Die Erstellungsregel : Wenn Sie sie erstellt haben, besitzen Sie sie und müssen sie freigeben.

Im Allgemeinen werden neue Cocoa-Programmierer durcheinander gebracht, indem sie nicht verstehen, welche Routinen ein Objekt mit einem zurückgeben retainCount > 0.

Hier ist ein Ausschnitt aus Very Simple Rules For Memory Management in Cocoa :

Regeln für die Anzahl der Aufbewahrungen

  • Innerhalb eines bestimmten Blocks sollte die Verwendung von -copy, -alloc und -retain gleich der Verwendung von -release und -autorelease sein.
  • Objekte, die mit Convenience-Konstruktoren erstellt wurden (z. B. stringWithString von NSString), gelten als automatisch freigegeben.
  • Implementieren Sie eine -dealloc-Methode, um Ihre eigenen Instanzvariablen freizugeben

Die erste Kugel sagt: Wenn Sie angerufen haben alloc(oder new fooCopy), müssen Sie die Freigabe für dieses Objekt aufrufen.

In der zweiten Aufzählung heißt es: Wenn Sie einen Convenience-Konstruktor verwenden und das Objekt herumhängen muss (wie bei einem Bild, das später gezeichnet wird), müssen Sie es beibehalten (und später freigeben).

Der 3. sollte selbsterklärend sein.


"Autorelease: Dokumente sagen, dass es irgendwann in der Zukunft eine Veröffentlichung auslösen wird." WANN?! " In diesem Punkt sind die Dokumente klar: "Autorelease bedeutet nur" Versenden einer Release-Nachricht später "(eine Definition von später finden Sie unter" Autorelease-Pools ")." Genau wann hängt vom Autorelease-Pool-Stack ab ...
mmalc

... "Das System kann das Objekt jederzeit nach dem aktuellen Ereigniszyklus freigeben." Dies lässt das System weniger deterministisch klingen als es ist ...
mmalc

... NSString foo = [self getBar]; // immer noch keine Notwendigkeit zu behalten oder freizugeben Dies ist falsch. Wer getBar aufruft, kennt die Implementierungsdetails nicht, daher sollte * beibehalten / freigeben (normalerweise über Accessoren), wenn er es außerhalb des aktuellen Bereichs verwenden möchte.
Mmalc

Der Artikel "Sehr einfache Regeln für die Speicherverwaltung in Cocoa" ist in mehrfacher Hinsicht veraltet - insbesondere "Objekte, die mit Convenience-Konstruktoren (z. B. stringWithString von NSString) erstellt wurden, gelten als automatisch freigegeben." ist nicht richtig - es ist einfach "nicht im Besitz des Empfängers".
Mmalc


0

Wie bereits von mehreren Personen erwähnt, ist Apples Einführung in die Speicherverwaltung bei weitem der beste Ausgangspunkt.

Ein nützlicher Link, den ich noch nicht erwähnt habe, ist Practical Memory Management . Sie finden es in der Mitte der Apple-Dokumente, wenn Sie sie durchlesen, aber es lohnt sich, direkt darauf zu verlinken. Es ist eine brillante Zusammenfassung der Speicherverwaltungsregeln mit Beispielen und häufigen Fehlern (im Grunde, was andere Antworten hier zu erklären versuchen, aber nicht so gut).

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.