Warum wird @autoreleasepool noch mit ARC benötigt?

Bei ARC (Automatic Reference Counting) müssen wir bei Objective-C-Objekten meist gar nicht über Speicherverwaltung nachdenken. Es ist nicht mehr erlaubt, NSAutoreleasePool s zu erstellen, jedoch gibt es eine neue Syntax:

 @autoreleasepool { … } 

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


EDIT: Um zu resümieren, was ich aus all den Antworten und Kommentaren auf den Punkt gebracht habe:

Neue Syntax:

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

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

Wichtiger:

  • ARC verwendet sowohl die autorelease als auch die release .
  • Dazu muss ein Pool für die automatische Freigabe vorhanden sein.
  • ARC erstellt den Pool für die automatische Freigabe nicht für Sie. Jedoch:
    • Der Hauptthread jeder Cocoa-App enthält bereits einen Autorelease-Pool.
  • Es gibt zwei @autoreleasepool denen Sie @autoreleasepool verwenden @autoreleasepool :
    1. Wenn Sie sich in einem sekundären Thread befinden und kein Pool für die automatische Freigabe vorhanden ist, müssen Sie Ihre eigenen myRunLoop(…) { @autoreleasepool { … } return success; } , um Lecks zu vermeiden, z. B. myRunLoop(…) { @autoreleasepool { … } return success; } myRunLoop(…) { @autoreleasepool { … } return success; } .
    2. Wenn Sie einen mehr lokalen Pool erstellen möchten, wie @mattjgalloway in seiner Antwort gezeigt hat.

    ARC wird keine Rücklagen, Freigaben und Autoreleases los, sondern fügt nur die benötigten hinzu. Es gibt also immer noch Anrufe zu behalten, es gibt immer noch Anrufe zu veröffentlichen, es gibt immer noch Anrufe zur Autorelease und es gibt immer noch automatische Freigabe-Pools.

    Eine der anderen Änderungen, die sie mit dem neuen Clang 3.0-Compiler und ARC vorgenommen haben, ist, dass sie NSAutoReleasePool durch die Compiler-Direktive NSAutoReleasePool ersetzt NSAutoReleasePool . NSAutoReleasePool war NSAutoReleasePool immer ein bisschen ein spezielles “Objekt” und sie haben es so gemacht, dass die Syntax der Verwendung von einem nicht mit einem Objekt verwechselt wird, so dass es im Allgemeinen ein bisschen einfacher ist.

    Sie benötigen also @autoreleasepool da es sich immer noch um Auto-Release-Pools handelt. Sie brauchen sich keine Gedanken über das Hinzufügen von autorelease Anrufen zu machen.

    Ein Beispiel für die Verwendung eines automatischen Release-Pools:

     - (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); } } } } 

    Ein sehr gelungenes Beispiel, @autoreleasepool , aber wenn du den @autoreleasepool innerhalb der äußeren for -loop hättest, dann würdest du später 100000000 Objekte anstatt 10000 jedes Mal um die äußere for -loop freigeben.

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

    Update: Ich habe mir die Interna von dem, was hier vor sich geht, angeschaut und es in meinem Blog aufgeschrieben . Wenn Sie einen Blick darauf casting, werden Sie genau sehen, was ARC macht und wie der neue Stil @autoreleasepool und wie er einen Bereich einführt, vom Compiler verwendet wird, um Informationen darüber zu erhalten, was beibehalten wird, welche Releases und Autoreleases benötigt werden.

    @autoreleasepool wird nichts automatisch freigeben. Es erstellt einen Autorelease-Pool, sodass bei Erreichen des Blockendes alle Objekte, die während des aktiven Blocks durch ARC automatisch freigegeben wurden, freigegeben werden. Apples Advanced Memory Management Programming Guide erklärt es so:

    Am Ende des Autorelease-Poolblocks werden Objekte, die eine Autorelease-Nachricht innerhalb des Blocks empfangen haben, eine Freigabenachricht gesendet – ein Objekt empfängt eine Freigabenachricht für jedes Mal, wenn es eine Autorelease-Nachricht innerhalb des Blocks gesendet hat.

    Leute missverstehen ARC oft für irgendeine Art von Müllsammlung oder dergleichen. Die Wahrheit ist, dass nach einiger Zeit Leute bei Apple (dank llvm und Clang-Projekten) realisiert haben, dass die Speicherverwaltung von Objective-C (alle retains und releases usw.) zur Kompilierzeit vollständig automatisiert werden kann . Dies ist nur durch das Lesen des Codes, noch bevor es ausgeführt wird! 🙂

    Um dies zu tun, gibt es nur eine Bedingung: Wir MÜSSEN den Regeln folgen, sonst wäre der Compiler nicht in der Lage den process zur Kompilierzeit zu automatisieren. Um sicherzustellen, dass wir die Regeln niemals brechen, dürfen wir nicht ausdrücklich release , retain , etc. schreiben. Diese Aufrufe werden automatisch vom Compiler in unseren Code eingefügt. Daher haben wir intern immer noch autorelease , autorelease , release , etc. Es ist nur so, dass wir sie nicht mehr schreiben müssen.

    Das A von ARC ist automatisch zur Kompilierungszeit, was viel besser ist als zur Laufzeit wie die Garbage Collection.

    Wir haben immer noch @autoreleasepool{...} weil es keine der Regeln bricht, sind wir frei, unseren Pool zu erstellen / zu leeren, wann immer wir ihn brauchen :).

    Das liegt daran, dass Sie dem Compiler immer noch Hinweise geben müssen, wann es sicher ist, dass automatisch freigegebene Objekte den Gültigkeitsbereich verlassen.

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

    Autorelease-Pool-Blöcke und Threads

    Jeder Thread in einer Cocoa-Anwendung verwaltet einen eigenen Stapel von Autorelease-Pool-Blöcken. Wenn Sie ein Foundation-only-Programm schreiben oder wenn Sie einen Thread trennen, müssen Sie einen eigenen Autorelease-Poolblock erstellen.

    Wenn Ihre Anwendung oder Ihr Thread langlebig ist und potenziell 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 unabhängiger Thread keine Cocoa-Aufrufe ausführt, müssen Sie keinen Poolpool für die automatische Freigabe verwenden.

    Hinweis: Wenn Sie sekundäre Threads mit den POSIX-Thread-APIs anstelle von NSThread erstellen, können Sie Cocoa nicht verwenden, es sei denn Cocoa befindet sich im Multithreading-Modus. Cocoa tritt erst dann in den Multithreading-Modus ein, wenn das erste NSThread-Objekt entfernt wurde. Um Cocoa für sekundäre POSIX-Threads zu verwenden, muss Ihre Anwendung zuerst mindestens ein NSThread-Objekt trennen, das sofort beendet werden kann. Sie können mit der NSThread-classnmethode isMultiThreaded testen, ob Cocoa im Multithreading-Modus ist.

    Bei der automatischen Referenzzählung oder ARC verwendet das System das gleiche Referenzzählsystem wie MRR, aber es fügt zur Kompilierungszeit die entsprechenden Speicherverwaltungsmethodenaufrufe ein. 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 unter Übergang in ARC-Versionshinweise.

    Autorelease-Pools sind erforderlich, um neu erstellte Objekte aus einer Methode zurückzugeben. ZB betrachten Sie dieses Stück Code:

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

    Die in der Methode erstellte Zeichenfolge hat eine Retain-Anzahl von eins. Wer soll nun die Zählung mit einer Freilassung ausgleichen?

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

    Der Aufrufer der Methode? Der Aufrufer erwartet nicht, ein Objekt abzurufen, das freigegeben werden muss. Der Methodenname impliziert nicht, dass ein neues Objekt erstellt wird. Er sagt nur, dass ein Objekt zurückgegeben wird und das zurückgegebene Objekt möglicherweise ein neues Objekt ist, das eine Freigabe erfordert Nun, sei ein existierender, der das nicht tut. Was die Methode zurückgibt, kann sogar von einem internen Zustand abhängen, so dass der Anrufer nicht wissen kann, ob er das Objekt freigeben muss und es sich nicht darum kümmern sollte.

    Wenn der Aufrufer immer alle zurückgegebenen Objekte per 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 wird wieder zurückgegeben. Dies würde in vielen Fällen sehr ineffizient sein, da es in vielen Fällen vollständig vermieden werden kann, Retain-Zählungen zu ändern, wenn der Aufrufer das zurückgegebene Objekt nicht immer freigeben wird.

    Deshalb gibt es Autorelease-Pools, also wird die erste Methode tatsächlich

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

    Durch den Aufruf der autorelease für ein Objekt wird diese dem Autorelease-Pool hinzugefügt, aber was bedeutet das eigentlich, ein Objekt zum Autorelease-Pool hinzuzufügen? Nun, es bedeutet, deinem System zu sagen: ” Ich möchte, dass du dieses Objekt für mich frei gibst, aber zu einem späteren Zeitpunkt, nicht jetzt; es hat eine Zurückhaltezahl, die durch eine Freigabe ausgeglichen werden muss, sonst wird die Erinnerung auslaufen, aber ich kann das selbst nicht Gerade jetzt, da ich brauche, dass das Objekt über meinen momentanen scope hinaus am Leben bleibt und mein Anrufer es auch nicht für mich tut, weiß es nicht, dass dies getan werden muss, also fügen Sie es Ihrem Pool hinzu und sobald Sie das aufräumen Pool, reinig auch mein Objekt für mich.

    Mit ARC entscheidet der Compiler für Sie, wann ein Objekt beibehalten werden soll, wann ein Objekt freigegeben werden soll und wann es einem Autorelease-Pool hinzugefügt werden soll. Es müssen jedoch immer noch Autorelease-Pools vorhanden sein, um neu erstellte Objekte aus Methoden ohne Speicherverlust zurückzugeben. Apple hat gerade einige raffinierte Optimierungen am generierten Code vorgenommen, die manchmal während der Laufzeit automatische Freigabe-Pools eliminieren. Diese Optimierungen erfordern, dass sowohl der Aufrufer als auch der Angerufene ARC verwenden (denken Sie daran, ARC zu mischen und nicht-ARC ist legal und wird auch offiziell unterstützt) und wenn das 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 Code, den das System generiert, kann sich entweder wie der folgende Code verhalten (das ist die sichere Version, mit der Sie ARC- und Nicht-ARC-Code beliebig 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 Retain / Release im Caller nur eine defensive Sicherheit ist, behalten Sie diese nicht unbedingt bei, der Code wäre ohne sie vollkommen korrekt)

    Oder es kann sich wie dieser Code verhalten, falls beide zur Verwendung von ARC zur Laufzeit erkannt werden:

     // Callee - (SomeObject *)getSomeObject { return [[SomeObject alloc] init]; } // Caller SomeObject * obj = [self getSomeObject]; [obj doStuff]; [obj release]; 

    Wie Sie sehen können, beseitigt Apple die Atomabgabe, also auch die verzögerte Objektfreigabe, wenn der Pool zerstört wird, sowie die Sicherheit, die er behält. Um mehr darüber zu erfahren, wie das möglich ist und was wirklich hinter den Kulissen vor sich geht, schau dir diesen Blogpost an.

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

    Für die meisten Entwickler gibt es heute nur noch einen Grund, dieses Konstrukt in ihrem Code zu verwenden und den Speicherbedarf klein zu halten. Betrachten Sie zum Beispiel diese Schleife:

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

    Angenommen, bei jedem Aufruf von tempObjectForData kann ein neues TempObject , für das die automatische TempObject zurückgegeben wird. Die for-Schleife erzeugt eine Million dieser temporären Objekte, die alle im aktuellen Autorespulepool gesammelt werden. Erst wenn dieser Pool zerstört ist, werden alle temporären Objekte ebenfalls zerstört. Bis das passiert, haben Sie eine Million dieser temporären Objekte im Speicher.

    Wenn Sie den Code stattdessen wie folgt schreiben:

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

    Dann wird bei jeder Ausführung der for-Schleife ein neuer Pool erstellt, der am Ende jeder Schleifeniteration zerstört wird. Auf diese Weise bleibt zu jeder Zeit höchstens ein temporäres Objekt im Speicher hängen, obwohl die Schleife eine Million Mal läuft.

    In der Vergangenheit musste man bei der Verwaltung von Threads (z. B. mit NSThread ) häufig auch Autorespeicherpools verwalten, da nur der Hauptthread automatisch einen Autorelease-Pool für eine Cocoa / UIKit App hat. Aber das ist heute so ziemlich ein Vermächtnis, denn heute würden Sie wahrscheinlich gar keine Threads verwenden. Sie würden GCD DispatchQueue oder NSOperationQueue und diese beiden verwalten einen Autorelease-Pool auf oberster Ebene für Sie, der vor dem Ausführen eines Blocks / Tasks erstellt und danach zerstört wird.

    Es scheint eine Menge Verwirrung zu diesem Thema zu geben (und mindestens 80 Leute, die wahrscheinlich jetzt 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 tut nichts Nützliches. ARC behandelt die Freigabe von Objekten zur richtigen Zeit. 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 Testing-Objekt 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; dies erlaubt uns nur, das Dealloc zu beobachten.) ARC verwendet keine Autorelease.

    Der Grund, warum @autoreleasepool noch erlaubt ist, ist für gemischte ARC- und Nicht-ARC-Projekte, die noch nicht vollständig auf ARC umgestellt wurden.

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

    Aber wenn Sie den ARC-Übergang vollständig gemacht haben, dann vergessen Sie den Autorelease-Pool.