Warum wird @autoreleasepool bei ARC noch benötigt?

191

Bei ARC (Automatic Reference Counting) müssen wir zum größten Teil überhaupt nicht an die Speicherverwaltung mit Objective-C-Objekten denken. Es ist nicht mehr erlaubt, NSAutoreleasePools zu erstellen , es gibt jedoch eine neue Syntax:

@autoreleasepool {
    
}

Meine Frage ist, warum sollte ich das jemals brauchen, wenn ich nicht manuell freigeben / automatisch freigeben soll?


EDIT: Um zusammenzufassen, was ich aus all den Antworten und Kommentaren herausgeholt habe:

Neue Syntax:

@autoreleasepool { … } ist eine neue Syntax für

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

[pool drain];

Wichtiger:

  • ARC verwendet autoreleaseebenso wie release.
  • Dazu ist ein Pool für die automatische Freigabe erforderlich.
  • ARC erstellt den Auto-Release-Pool nicht für Sie. Jedoch:
    • Der Haupt-Thread jeder Cocoa-App enthält bereits einen Autorelease-Pool.
  • Es gibt zwei Fälle, in denen Sie möglicherweise davon Gebrauch machen möchten @autoreleasepool:
    1. Wenn Sie sich in einem sekundären Thread befinden und kein Pool für die automatische Freigabe vorhanden ist, müssen Sie einen eigenen erstellen, um Lecks zu vermeiden, z myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. Wenn Sie einen lokaleren Pool erstellen möchten, wie @mattjgalloway in seiner Antwort gezeigt hat.
mk12
quelle
1
Es gibt auch eine dritte Gelegenheit: Wenn Sie etwas entwickeln, das nicht mit UIKit oder NSFoundation zusammenhängt. Etwas, das Kommandozeilen-Tools oder so verwendet
Garnik

Antworten:

214

ARC entfernt keine Retains, Releases und Autoreleases, sondern fügt nur die für Sie erforderlichen hinzu. Es gibt also immer noch Aufrufe zum Beibehalten, es gibt immer noch Aufrufe zum Freigeben, es gibt immer noch Aufrufe zum Autorelease und es gibt immer noch Pools für die automatische Freigabe.

Eine der anderen Änderungen, die sie mit dem neuen Clang 3.0-Compiler und ARC vorgenommen haben, ist, dass sie durch NSAutoReleasePooldie @autoreleasepoolCompiler-Direktive ersetzt wurden. NSAutoReleasePoolwar sowieso immer ein bisschen ein spezielles "Objekt" und sie haben es so gemacht, dass die Syntax der Verwendung eines Objekts nicht mit einem Objekt verwechselt wird, so dass es im Allgemeinen etwas einfacher ist.

Grundsätzlich brauchen Sie also, @autoreleasepoolweil es immer noch Pools mit automatischer Freigabe gibt, über die Sie sich Sorgen machen müssen. Sie müssen sich nur nicht um das Hinzufügen von autoreleaseAnrufen kümmern .

Ein Beispiel für die Verwendung eines Pools mit automatischer Freigabe:

- (void)useALoadOfNumbers {
    for (int j = 0; j < 10000; ++j) {
        @autoreleasepool {
            for (int i = 0; i < 10000; ++i) {
                NSNumber *number = [NSNumber numberWithInt:(i+j)];
                NSLog(@"number = %p", number);
            }
        }
    }
}

Sicherlich ein äußerst ausgeklügeltes Beispiel, aber wenn Sie nicht das @autoreleasepoolInnere der äußeren forSchleife hätten, würden Sie später 100000000 Objekte freigeben, anstatt jedes Mal 10000 um die äußere forSchleife herum.

Update: Siehe auch diese Antwort - https://stackoverflow.com/a/7950636/1068248 - warum @autoreleasepooles nichts mit ARC zu tun hat.

Update: Ich habe mir die Interna angesehen, was hier vor sich geht, und es in meinem Blog geschrieben . Wenn Sie dort einen Blick darauf werfen, werden Sie genau sehen, was ARC tut und wie @autoreleasepoolder Compiler den neuen Stil und die Einführung eines Bereichs verwendet, um Informationen darüber abzuleiten, welche Aufbewahrungen, Releases und Autoreleases erforderlich sind.

mattjgalloway
quelle
11
Es wird nicht zurückbehalten. Es fügt sie für Sie hinzu. Die Referenzzählung läuft noch, sie erfolgt nur automatisch. Daher automatische Referenzzählung :-D.
Mattjgalloway
6
Warum fügt es das nicht auch @autoreleasepoolfür mich hinzu? Wenn ich nicht kontrolliere, was automatisch freigegeben oder freigegeben wird (ARC erledigt das für mich), wie sollte ich wissen, wann ich einen Autorelease-Pool einrichten muss?
mk12
5
Sie haben jedoch die Kontrolle darüber, wo sich Ihre Pools für die automatische Freigabe noch befinden. Standardmäßig ist eine App um Ihre gesamte App gewickelt, aber Sie möchten vielleicht mehr.
Mattjgalloway
5
Gute Frage. Sie müssen nur "wissen". Stellen Sie sich vor, Sie fügen einen hinzu, ähnlich wie man in einer GC-Sprache einem Garbage Collector einen Hinweis hinzufügen könnte, um jetzt einen Sammelzyklus auszuführen. Vielleicht wissen Sie, dass eine Menge Objekte zum Löschen bereit sind. Sie haben eine Schleife, die eine Reihe von temporären Objekten zuweist, sodass Sie "wissen" (oder Instrumente sagen Ihnen möglicherweise :), dass das Hinzufügen eines Release-Pools um die Schleife eine wäre gute Idee.
Graham Perks
6
Das Schleifenbeispiel funktioniert ohne Autorelease einwandfrei: Jedes Objekt wird freigegeben, wenn die Variable den Gültigkeitsbereich verlässt. Das Ausführen des Codes ohne Autorelease beansprucht eine konstante Menge an Speicher und zeigt an, dass Zeiger wiederverwendet werden. Wenn Sie einen Haltepunkt auf das Dealloc eines Objekts setzen, wird dieser jedes Mal einmal durch die Schleife aufgerufen, wenn objc_storeStrong aufgerufen wird. Vielleicht macht OSX hier etwas Dummes, aber Autoreleasepool ist unter iOS völlig unnötig.
Glenn Maynard
16

@autoreleasepoolveröffentlicht nichts automatisch. Es wird ein Autorelease-Pool erstellt, sodass bei Erreichen des Blockendes alle Objekte, die von ARC automatisch freigegeben wurden, während der Block aktiv war, Freigabemeldungen gesendet werden. Das Advanced Memory Management-Programmierhandbuch von Apple erklärt dies folgendermaßen:

Am Ende des Autorelease-Poolblocks wird Objekten, die eine Autorelease-Nachricht innerhalb des Blocks empfangen haben, eine Release-Nachricht gesendet. Ein Objekt erhält eine Release-Nachricht für jedes Mal, wenn eine Autorelease-Nachricht innerhalb des Blocks gesendet wurde.

outis
quelle
1
Nicht unbedingt. Das Objekt erhält eine releaseNachricht, aber wenn die Anzahl der Aufbewahrungen> 1 ist, wird das Objekt NICHT freigegeben.
Andybons
@andybons: aktualisiert; Vielen Dank. Ist dies eine Änderung gegenüber dem Verhalten vor ARC?
Outis
Das ist falsch. Von ARC freigegebene Objekte erhalten Freigabemeldungen, sobald sie von ARC freigegeben wurden, mit oder ohne Autorelease-Pool.
Glenn Maynard
7

Menschen verstehen ARC oft falsch für irgendeine Art von Müllabfuhr oder dergleichen. Die Wahrheit ist, dass die Mitarbeiter von Apple nach einiger Zeit (dank llvm- und clang-Projekten) erkannt haben, dass die Speicherverwaltung von Objective-C (alle retainsund releasesusw.) zur Kompilierungszeit vollständig automatisiert werden kann . Dies geschieht, indem Sie den Code lesen, noch bevor er ausgeführt wird! :) :)

Dazu gibt es nur eine Bedingung: Wir MÜSSEN die Regeln befolgen , sonst könnte der Compiler den Prozess zur Kompilierungszeit nicht automatisieren. Also, um sicherzustellen , dass wir nie die Regeln brechen, werden wir nicht explizit Schreib erlaubt release, retainusw. Diese Anrufe werden automatisch in unseren Code durch den Compiler injiziert. Daher intern noch wir haben autoreleases, retain, releaseusw. Es ist nur brauchen wir sie nicht mehr zu schreiben.

Das A von ARC erfolgt zur Kompilierungszeit automatisch, was viel besser ist als zur Laufzeit wie bei der Speicherbereinigung.

Wir haben immer noch, @autoreleasepool{...}weil es keine der Regeln verletzt, wir können unseren Pool jederzeit erstellen / entleeren, wann immer wir es brauchen :).

nacho4d
quelle
1
ARC ist ein Referenzzähl-GC, kein Mark-and-Sweep-GC wie in JavaScript und Java, aber es ist definitiv eine Speicherbereinigung. Damit wird die Frage nicht beantwortet - "Sie können" beantwortet nicht die Frage "Warum sollten Sie". Das solltest du nicht.
Glenn Maynard
3

Dies liegt daran, dass Sie dem Compiler immer noch Hinweise geben müssen, wann sicher freigegebene Objekte den Gültigkeitsbereich verlassen können.

DougW
quelle
Können Sie mir ein Beispiel geben, wann Sie dies tun müssten?
mk12
Vor ARC hatte ich beispielsweise einen CVDisplayLink, der auf einem sekundären Thread für meine OpenGL-App ausgeführt wurde, aber ich habe keinen Autorelease-Pool in seinem Runloop erstellt, weil ich wusste, dass ich nichts automatisch freigebe (oder Bibliotheken verwende, die dies tun). Bedeutet das, dass ich jetzt etwas hinzufügen muss, @autoreleasepoolweil ich nicht weiß, ob ARC sich möglicherweise dazu entschließt, etwas automatisch freizugeben?
mk12
@ Mk12 - Nein. Sie haben immer noch einen Pool für die automatische Freigabe, der jedes Mal um die Hauptlaufschleife herum entleert wird. Sie sollten nur dann eine hinzufügen müssen, wenn Sie sicherstellen möchten, dass automatisch freigegebene Objekte entleert werden, bevor sie es sonst tun würden - beispielsweise beim nächsten Durchlaufen der Ausführungsschleife.
Mattjgalloway
2
@DougW - Ich habe mir angesehen, was der Compiler tatsächlich tut, und hier darüber gebloggt - iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. Hoffentlich wird erklärt, was sowohl zur Kompilierungs- als auch zur Laufzeit vor sich geht.
Mattjgalloway
2

Zitiert aus https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html :

Autorelease-Poolblöcke und -Threads

Jeder Thread in einer Cocoa-Anwendung verwaltet seinen eigenen Stapel von Autorelease-Poolblöcken. Wenn Sie ein Nur-Foundation-Programm schreiben oder einen Thread trennen, müssen Sie Ihren eigenen Autorelease-Poolblock erstellen.

Wenn Ihre Anwendung oder Ihr Thread langlebig ist und möglicherweise viele automatisch freigegebene Objekte generiert, sollten Sie Autorelease-Poolblöcke verwenden (wie AppKit und UIKit im Hauptthread). Andernfalls sammeln sich automatisch freigegebene Objekte an und Ihr Speicherbedarf wächst. Wenn Ihr getrennter Thread keine Cocoa-Aufrufe ausführt, müssen Sie keinen Autorelease-Poolblock verwenden.

Hinweis: Wenn Sie sekundäre Threads mithilfe der POSIX-Thread-APIs anstelle von NSThread erstellen, können Sie Cocoa nur verwenden, wenn sich Cocoa im Multithreading-Modus befindet. Cocoa wechselt erst nach dem Trennen seines ersten NSThread-Objekts in den Multithreading-Modus. Um Cocoa für sekundäre POSIX-Threads zu verwenden, muss Ihre Anwendung zuerst mindestens ein NSThread-Objekt trennen, das sofort beendet werden kann. Mit der NSThread-Klassenmethode isMultiThreaded können Sie testen, ob sich Cocoa im Multithreading-Modus befindet.

...

Bei der automatischen Referenzzählung (ARC) verwendet das System dasselbe Referenzzählsystem wie die MRR, fügt jedoch zur Kompilierungszeit die entsprechende Speicherverwaltungsmethode ein, die für Sie aufgerufen wird. Es wird dringend empfohlen, ARC für neue Projekte zu verwenden. Wenn Sie ARC verwenden, ist es normalerweise nicht erforderlich, die in diesem Dokument beschriebene zugrunde liegende Implementierung zu verstehen, obwohl dies in einigen Situationen hilfreich sein kann. Weitere Informationen zu ARC finden Sie in den Versionshinweisen zu ARC.

Raunak
quelle
2

Autorelease-Pools sind erforderlich, um neu erstellte Objekte von einer Methode zurückzugeben. Betrachten Sie zB diesen Code:

- (NSString *)messageOfTheDay {
    return [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
}

Die in der Methode erstellte Zeichenfolge hat eine Aufbewahrungszahl von eins. Wer soll nun diese Anzahl mit einer Freigabe ausgleichen?

Die Methode selbst? Nicht möglich, es muss das erstellte Objekt zurückgeben, daher darf es nicht vor der Rückgabe freigegeben werden.

Der Aufrufer der Methode? Der Aufrufer erwartet nicht, dass ein Objekt abgerufen wird, das freigegeben werden muss. Der Methodenname bedeutet nicht, dass ein neues Objekt erstellt wird. Er gibt lediglich an, dass ein Objekt zurückgegeben wird, und dieses zurückgegebene Objekt kann ein neues Objekt sein, für das eine Freigabe erforderlich ist Nun, es gibt eine, die es nicht tut. Was die Methode zurückgibt, hängt möglicherweise sogar von einem internen Status ab, sodass der Aufrufer nicht wissen kann, ob er dieses Objekt freigeben muss und sich nicht darum kümmern muss.

Wenn der Aufrufer immer alle zurückgegebenen Objekte gemäß Konvention freigeben müsste, müsste jedes nicht neu erstellte Objekt immer beibehalten werden, bevor es von einer Methode zurückgegeben wird, und es müsste vom Aufrufer freigegeben werden, sobald es den Gültigkeitsbereich verlässt, es sei denn es wird wieder zurückgegeben. Dies wäre in vielen Fällen äußerst ineffizient, da in vielen Fällen eine Änderung der Aufbewahrungsanzahl vollständig vermieden werden kann, wenn der Aufrufer das zurückgegebene Objekt nicht immer freigibt.

Aus diesem Grund gibt es Autorelease-Pools, sodass die erste Methode tatsächlich wird

- (NSString *)messageOfTheDay {
    NSString * res = [[NSString alloc] initWithFormat:@"Hello %@!", self.username];
    return [res autorelease];
}

Der Aufruf autoreleaseauf einem Objekt fügt sie dem Autofreigabepool, aber was bedeutet das wirklich Mittel, um ein Objekt zu dem Autofreigabepool hinzufügen? Nun, es bedeutet, Ihrem System zu sagen: " Ich möchte, dass Sie dieses Objekt für mich freigeben, aber zu einem späteren Zeitpunkt, nicht jetzt. Es hat eine Aufbewahrungszahl, die durch eine Freigabe ausgeglichen werden muss, da sonst Speicherplatz verloren geht, aber ich kann das nicht selbst tun." Im Moment, da ich das Objekt brauche, um über meinen derzeitigen Bereich hinaus am Leben zu bleiben, und mein Anrufer es auch nicht für mich tun wird, weiß er nicht, dass dies getan werden muss. Fügen Sie es also Ihrem Pool hinzu und sobald Sie es aufgeräumt haben Pool, auch mein Objekt für mich aufräumen. "

Mit ARC entscheidet der Compiler für Sie, wann ein Objekt beibehalten, wann ein Objekt freigegeben und wann es einem Autorelease-Pool hinzugefügt werden soll. Es sind jedoch weiterhin Autorelease-Pools erforderlich, um neu erstellte Objekte von Methoden zurückgeben zu können, ohne Speicher zu verlieren. Apple hat gerade einige raffinierte Optimierungen am generierten Code vorgenommen, die manchmal Autorelease-Pools zur Laufzeit eliminieren. Diese Optimierungen erfordern, dass sowohl der Anrufer als auch der Angerufene ARC verwenden (denken Sie daran, dass das Mischen von ARC und Nicht-ARC legal ist und auch offiziell unterstützt wird) und ob dies tatsächlich der Fall ist, kann nur zur Laufzeit bekannt sein.

Betrachten Sie diesen ARC-Code:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];

Der vom System generierte Code kann sich entweder wie der folgende Code verhalten (dies ist die sichere Version, mit der Sie ARC- und Nicht-ARC-Code frei mischen können):

// Callee
- (SomeObject *)getSomeObject {
    return [[[SomeObject alloc] init] autorelease];
}

// Caller
SomeObject * obj = [[self getSomeObject] retain];
[obj doStuff];
[obj release];

(Beachten Sie, dass die Aufbewahrung / Freigabe im Anrufer nur eine defensive Sicherheitsaufbewahrung ist. Sie ist nicht unbedingt erforderlich. Ohne sie wäre der Code vollkommen korrekt.)

Oder es kann sich wie dieser Code verhalten, falls erkannt wird, dass beide zur Laufzeit ARC verwenden:

// Callee
- (SomeObject *)getSomeObject {
    return [[SomeObject alloc] init];
}

// Caller
SomeObject * obj = [self getSomeObject];
[obj doStuff];
[obj release];

Wie Sie sehen können, eliminiert Apple die Atuorelease, also auch die verzögerte Objektfreigabe, wenn der Pool zerstört wird, sowie die Sicherheit. Um mehr darüber zu erfahren, wie das möglich ist und was wirklich hinter den Kulissen passiert, lesen Sie diesen Blog-Beitrag.

Nun zur eigentlichen Frage: Warum sollte man verwenden @autoreleasepool?

Für die meisten Entwickler gibt es heute nur noch einen Grund, dieses Konstrukt in ihrem Code zu verwenden, nämlich den Speicherbedarf gegebenenfalls gering zu halten. Betrachten Sie zB diese Schleife:

for (int i = 0; i < 1000000; i++) {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Angenommen, jeder Aufruf von tempObjectForDatakann eine neue erstellen TempObject, die automatisch freigegeben wird. Die for-Schleife erstellt eine Million dieser temporären Objekte, die alle im aktuellen Autorelease-Pool gesammelt werden. Erst wenn dieser Pool zerstört wird, werden auch alle temporären Objekte zerstört. Bis dahin haben Sie eine Million dieser temporären Objekte im Speicher.

Wenn Sie den Code stattdessen so schreiben:

for (int i = 0; i < 1000000; i++) @autoreleasepool {
    // ... code ...
    TempObject * to = [TempObject tempObjectForData:...];
    // ... do something with to ...
}

Dann wird jedes Mal, wenn die for-Schleife ausgeführt wird, ein neuer Pool erstellt und am Ende jeder Schleifeniteration zerstört. Auf diese Weise bleibt zu jeder Zeit höchstens ein temporäres Objekt im Speicher hängen, obwohl die Schleife eine Million Mal ausgeführt wird.

In der Vergangenheit mussten Sie beim Verwalten von Threads (z. B. Verwenden NSThread) häufig auch Autorelease-Pools selbst verwalten, da nur der Haupt-Thread automatisch einen Autorelease-Pool für eine Cocoa / UIKit-App hat. Dies ist heute jedoch so ziemlich ein Vermächtnis, da Sie heute wahrscheinlich zunächst keine Threads verwenden würden. Sie würden GCDs DispatchQueueoder NSOperationQueue's verwenden und diese beiden verwalten einen Autorelease-Pool der obersten Ebene für Sie, der vor dem Ausführen eines Blocks / einer Aufgabe erstellt und nach Abschluss zerstört wurde.

Mecki
quelle
-4

Es scheint viel Verwirrung zu diesem Thema zu geben (und mindestens 80 Leute, die jetzt wahrscheinlich verwirrt sind und denken, dass sie @autoreleasepool um ihren Code streuen müssen).

Wenn ein Projekt (einschließlich seiner Abhängigkeiten) ausschließlich ARC verwendet, muss @autoreleasepool niemals verwendet werden und wird nichts Nützliches tun. ARC behandelt das Freigeben von Objekten zum richtigen Zeitpunkt. Beispielsweise:

@interface Testing: NSObject
+ (void) test;
@end

@implementation Testing
- (void) dealloc { NSLog(@"dealloc"); }

+ (void) test
{
    while(true) NSLog(@"p = %p", [Testing new]);
}
@end

zeigt an:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

Jedes Testobjekt wird freigegeben, sobald der Wert den Gültigkeitsbereich verlässt, ohne darauf zu warten, dass ein Autorelease-Pool beendet wird. (Dasselbe passiert mit dem NSNumber-Beispiel. Dadurch können wir nur den Dealloc beobachten.) ARC verwendet keine Autorelease.

Der Grund, warum @autoreleasepool weiterhin zulässig ist, sind gemischte ARC- und Nicht-ARC-Projekte, die noch nicht vollständig auf ARC umgestellt wurden.

Wenn Sie Nicht-ARC-Code aufrufen, wird möglicherweise ein automatisch freigegebenes Objekt zurückgegeben. In diesem Fall würde die obige Schleife lecken, da der aktuelle Autorelease-Pool niemals verlassen wird. Dort möchten Sie einen @autoreleasepool um den Codeblock legen.

Wenn Sie den ARC-Übergang vollständig abgeschlossen haben, vergessen Sie den Autoreleasepool.

Glenn Maynard
quelle
4
Diese Antwort ist falsch und widerspricht auch der ARC-Dokumentation. Ihre Beweise sind anekdotisch, da Sie zufällig eine Zuweisungsmethode verwenden, die der Compiler nicht automatisch freigibt. Sie können sehr leicht feststellen, dass dies nicht funktioniert, wenn Sie einen neuen statischen Initialisierer für Ihre benutzerdefinierte Klasse erstellen. Erstellen Sie diesen Initialisierer und verwenden Sie ihn in Ihrer Schleife : + (Testing *) testing { return [Testing new] }. Dann werden Sie sehen, dass der Dealloc erst später aufgerufen wird. Dies wird behoben, wenn Sie das Innere der Schleife in einen @autoreleasepoolBlock einschließen.
Dima
@Dima Versucht unter iOS10, Dealloc wird sofort nach dem Drucken der Objektadresse aufgerufen. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC
@KudoCC - Ich auch, und ich habe das gleiche Verhalten gesehen wie Sie. Aber als ich mich [UIImage imageWithData]in die Gleichung einmischte, bemerkte ich plötzlich das traditionelle autoreleaseVerhalten und musste das @autoreleasepoolmaximale Gedächtnis auf einem vernünftigen Niveau halten.
Rob
@Rob Ich kann mir nicht helfen, den Link hinzuzufügen .
KudoCC