Dlaczego @autoreleasepool jest nadal potrzebny Z ARC?

W większości przypadków ARC (automatyczne liczenie odniesień), nie musimy myśleć o zarządzaniu pamięcią w obiektach Objective-C. Nie wolno już tworzyć NSAutoreleasePool s, jednak istnieje nowa składnia:

@autoreleasepool {
    …
}

Moje pytanie brzmi, po co mi to, skoro nie powinienem ręcznie zwalniać/automatyzować ?


EDIT: Podsumowując to co dostałem od wszystkich anwerów i komentarze zwięźle:

Nowe Składnia:

@autoreleasepool { … } jest nową składnią dla

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

Ważniejsze:

  • Arc używa autorelease jak również release.
  • aby to zrobić, potrzebuje puli automatycznych zwolnień.
  • ARC nie tworzy dla Ciebie puli automatycznych wydań. jednakże:
    • główny wątek każdej aplikacji Cocoa ma już pulę autorelease.
  • są dwie okazje, kiedy możesz chcieć skorzystać z @autoreleasepool:
    1. kiedy są w wątku wtórnym i nie ma puli automatycznego uwalniania, musisz zrobić własną, aby zapobiec wyciekom, takim jak myRunLoop(…) { @autoreleasepool { … } return success; }.
    2. kiedy chcesz stworzyć bardziej lokalną pulę, jak pokazał @mattjgalloway w swojej odpowiedzi.
Author: Alexander Abakumov, 2012-02-01

7 answers

ARC nie pozbywa się zatrzymań, wydań i autoreleaz, po prostu dodaje wymagane dla Ciebie. Tak więc nadal są połączenia do zachowania, nadal są połączenia do wydania, nadal są połączenia do autorelease i nadal są pule automatycznego wydania.

Jedną z innych zmian wprowadzonych w nowym kompilatorze Clang 3.0 i ARC jest zastąpienie {[1] } dyrektywą kompilatora @autoreleasepool. NSAutoReleasePool zawsze był jakiś specjalny "obiekt" i zrobili to tak, że składnia używanie jednego z nich nie jest mylone z obiektem, więc generalnie jest nieco prostsze.

Więc zasadniczo, potrzebujesz @autoreleasepool, ponieważ nadal istnieją pule automatycznego Wydania, o które musisz się martwić. Po prostu nie musisz się martwić o dodawanie autorelease połączeń.

Przykład użycia puli automatycznych zwolnień:
- (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);
            }
        }
    }
}

Bardzo wymyślny przykład, oczywiście, ale gdybyś nie miał @autoreleasepool wewnątrz zewnętrznej for - pętli, to zwolniłbyś później 100000000 obiektów, a nie 10000 za każdym razem zewnętrzna for - pętla.

Aktualizacja: Zobacz też tę odpowiedź - https://stackoverflow.com/a/7950636/1068248 - dlaczego @autoreleasepool nie ma nic wspólnego z ARC.

Aktualizacja: Zajrzałem do wnętrza tego, co się tutaj dzieje i napisałem to na moim blogu. Jeśli spojrzysz tam, zobaczysz dokładnie, co robi ARC i jak Nowy Styl @autoreleasepool i jak wprowadza zakres jest używany przez kompilator do wnioskowania o tym, co zachowuje, wymagane są Wydania i autoreleazy.

 200
Author: mattjgalloway,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:07

@autoreleasepool nie powoduje autorelekcji. Tworzy pulę autorelease, tak że po osiągnięciu końca bloku wszystkie obiekty, które były autoreleasowane przez ARC podczas gdy blok był aktywny, będą wysyłane komunikaty o wydaniu. Apple ' S Advanced Memory Management Programming Guide wyjaśnia to tak:

Na końcu bloku puli autorelease obiekty, które otrzymały wiadomość autorelease wewnątrz bloku, otrzymują komunikat release-obiekt otrzymuje komunikat release dla za każdym razem był wysyłany komunikat autorelease wewnątrz bloku.

 13
Author: outis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-19 20:48:10

Ludzie często źle rozumieją ARC za jakiś wywóz śmieci lub tym podobne. Prawda jest taka, że po pewnym czasie ludzie z Apple (dzięki projektom llvm i clang) zdali sobie sprawę, że Objective-C administruje pamięcią (Wszystkie retains i releases itd.) może być w pełni zautomatyzowany w czasie kompilacji . To jest, po prostu czytając kod, nawet przed uruchomieniem! :)

Aby to zrobić jest tylko jeden warunek: musimy przestrzegać zasad , w przeciwnym razie kompilator by nie można zautomatyzować procesu w czasie kompilacji. Tak więc, aby upewnić się, że nigdy nie łamiemy zasad, nie wolno nam wyraźnie pisać release, retain, itd. Wywołania te są automatycznie wprowadzane do naszego kodu przez kompilator. Stąd wewnętrznie wciąż mamy autorelease s, retain, release, itd. Po prostu nie musimy już ich pisać.

A of ARC jest automatyczne w czasie kompilacji, co jest znacznie lepsze niż w czasie uruchamiania, jak np. garbage collection.

Wciąż mamy @autoreleasepool{...} ponieważ posiadanie go nie łamie żadnej z zasad, jesteśmy wolni tworzyć/drenować nasz basen, kiedy tylko go potrzebujemy :).

 6
Author: nacho4d,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-31 08:06:59

To dlatego, że nadal musisz DOSTARCZYĆ kompilatorowi podpowiedzi o tym, kiedy jest bezpieczne dla obiektów z autorelementem, aby wyjść poza zakres.

 3
Author: DougW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-31 21:05:08

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

Autorelease Pool Blocks and Threads

Każdy wątek w aplikacji Cocoa utrzymuje swój własny stos autorelease pool blocks. Jeśli piszesz Program tylko dla Fundacji lub jeśli odłączysz wątek, musisz utworzyć własną autorelease blok basenowy.

Jeśli Twoja aplikacja lub wątek jest długotrwały i potencjalnie generuje wiele obiektów z autorelease, powinieneś użyć bloków puli z autorelease (podobnie jak AppKit i UIKit w głównym wątku); w przeciwnym razie autoreleased obiekty gromadzą się, a Twój ślad pamięci rośnie. Jeśli twój odłączony wątek nie wykonuje połączeń kakaowych, nie trzeba używać blok basenu autorelease.

Uwaga: jeśli tworzysz wątki wtórne za pomocą interfejsów API wątków POSIX zamiast Nsthread nie można używać kakao, chyba że kakao jest w tryb wielowątkowy. Kakao wchodzi w tryb wielowątkowości dopiero po odłączenie pierwszego obiektu NSThread. Aby użyć Cocoa na drugorzędnym POSIX wątków, Twoja aplikacja musi najpierw odłączyć co najmniej jeden NSThread obiekt, który może natychmiast wyjść. Możesz sprawdzić, czy kakao jest w tryb wielowątkowy z metodą klasy nsthread isMultiThreaded.

...

W automatycznym zliczaniu referencji, czyli łuku, system używa tego samego system zliczania odniesienia jako MRR, ale wstawia odpowiednią pamięć wywołania metody zarządzania dla Ciebie w czasie kompilacji. Jesteś mocno zachęcamy do korzystania z ARC dla nowych projektów. Jeśli używasz ARC, jest zazwyczaj nie ma potrzeby, aby zrozumieć podstawową implementację opisane w niniejszym dokumencie, choć w niektórych sytuacjach może być pomocne. Aby uzyskać więcej informacji na temat ARC, zobacz przechodzenie do informacji o wydaniu ARC.

 1
Author: Raunak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-18 09:36:04

Pule Autoelementów są wymagane do zwracania nowo utworzonych obiektów z metody. Np. rozważ ten fragment kodu:

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

Łańcuch utworzony w metodzie będzie miał liczbę zatrzymań równą 1. Kto teraz zbilansuje tę liczbę z uwolnieniem?

Sama metoda? Nie jest to możliwe, musi zwrócić utworzony obiekt, więc nie może go zwolnić przed powrotem.

Rozmówca metody? Wywołujący nie spodziewa się odzyskać obiektu, który potrzebuje zwolnienie, nazwa metody nie oznacza, że został utworzony nowy obiekt, tylko mówi, że obiekt jest zwracany i ten zwracany obiekt może być nowym wymagającym wydania, ale równie dobrze może być już istniejącym, który tego nie robi. to, co zwraca metoda, może zależeć nawet od jakiegoś stanu wewnętrznego, więc wywołujący nie może wiedzieć, czy musi zwolnić ten obiekt i nie powinien się tym przejmować.

Jeśli wywołujący musi zawsze zwolnić wszystkie zwracane obiekty zgodnie z konwencją, to każdy obiekt nie nowo utworzony musiałby być zawsze zachowany przed zwróceniem go z metody i musiałby być zwolniony przez wywołującego, gdy wyjdzie poza zakres, chyba że zostanie zwrócony ponownie. Byłoby to wysoce nieefektywne w wielu przypadkach, ponieważ można całkowicie uniknąć zmiany liczby zatrzymań w wielu przypadkach, jeśli wywołujący nie zawsze zwolni zwracany obiekt.

Dlatego istnieją pule autoelease, więc pierwsza metoda w rzeczywistości stanie się

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

Wywołanie autorelease na obiekcie dodaje go do puli autorelease, ale co to tak naprawdę znaczy, dodając obiekt do puli autorelease? Cóż, oznacza to powiedzenie twojemu systemowi " chcę, abyś zwolnił ten obiekt dla mnie, ale w pewnym późniejszym czasie, nie teraz; ma liczbę zatrzymań, która musi być zrównoważona przez zwolnienie, w przeciwnym razie pamięć wycieknie, ale nie mogę tego zrobić sam teraz, ponieważ potrzebuję obiektu, aby pozostać przy życiu poza moim obecnym zasięgiem i mój rozmówca też nie zrobi tego za mnie, nie ma wiedzy, że musi to zostać usunięte. załatwione. Więc dodaj go do swojego basenu, a gdy posprzątasz ten Basen, Wyczyść również mój obiekt za mnie."

Z ARC kompilator decyduje za Ciebie kiedy zachować obiekt, kiedy zwolnić obiekt i kiedy dodać go do puli autorelease, ale nadal wymaga obecności pul autorelease, aby móc zwracać nowo utworzone obiekty z metod bez wycieku pamięci. Apple właśnie wprowadziło kilka sprytnych optymalizacji do generowanego kodu, które czasami eliminują pule autorelease podczas wykonywania. Te optymalizacje wymagają, aby zarówno wywołujący, jak i wywołujący używali ARC (pamiętaj, że mieszanie ARC i non-ARC jest legalne i również oficjalnie obsługiwane), a jeśli tak jest, to może być znane tylko w czasie wykonywania.

Rozważmy ten kod łuku:

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

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

Kod, który generuje system, może zachowywać się jak następujący kod (czyli bezpieczna wersja, która pozwala na dowolne mieszanie kodu ARC i non-ARC):

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

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

(zwróć uwagę na zachowanie/zwolnienie w wywołanie jest tylko zabezpieczeniem obronnym, nie jest ściśle wymagane, kod byłby idealnie poprawny bez niego)

Lub może zachowywać się tak jak ten kod, w przypadku gdy oba zostaną wykryte, aby używać ARC w czasie wykonywania:

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

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

Jak widać, Apple eliminuje atuorelease, a więc również opóźnione zwolnienie obiektu po zniszczeniu puli, a także zachowanie bezpieczeństwa. Aby dowiedzieć się więcej o tym, jak to jest możliwe i co tak naprawdę dzieje się za kulisami, zajrzyj na ten blog poczta.

A teraz do pytania: po co używać @autoreleasepool?

Dla większości programistów, jest tylko jeden powód, aby używać tej konstrukcji w swoim kodzie, a jest to, aby zachować mały ślad pamięci tam, gdzie ma to zastosowanie. Np. rozważmy tę pętlę:

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

Załóżmy, że każde wywołanie tempObjectForData może utworzyć nową TempObject, która jest zwracana automatycznie. Pętla for utworzy milion tych obiektów temp, które są gromadzone w bieżącym programie autoreleasepool i tylko wtedy, gdy ten Basen zostanie zniszczony, wszystkie obiekty temp również zostaną zniszczone. Do tego czasu masz milion tych tymczasowych obiektów w pamięci.

Jeśli zamiast tego napiszesz kod w ten sposób:

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

Wtedy nowa pula jest tworzona za każdym razem, gdy pętla for jest uruchamiana i niszczona na końcu każdej iteracji pętli. W ten sposób co najwyżej jeden obiekt tymczasowy jest w pamięci w dowolnym momencie, mimo że pętla działa milion razy.

W przeszłości często musiałeś możesz również samodzielnie zarządzać autoreleasepools podczas zarządzania wątkami (np. używając NSThread), ponieważ tylko główny wątek ma automatycznie pulę autorelease dla aplikacji Cocoa / UIKit. Jednak jest to dziś prawie spuścizna, ponieważ prawdopodobnie nie użyłbyś wątków na początek. Można użyć GCD DispatchQueue'S lub NSOperationQueue' s i te dwa oba zarządzać najwyższy poziom autorelease puli dla Ciebie, utworzony przed uruchomieniem bloku / zadania i zniszczone po jego zakończeniu.

 0
Author: Mecki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-09 19:13:26

Wydaje się, że jest dużo zamieszania w tym temacie (i co najmniej 80 osób, które prawdopodobnie są teraz zdezorientowane w tym temacie i myślą, że muszą posypać @autoreleasepool swoim kodem).

Jeśli projekt (w tym jego zależności) używa wyłącznie ARC, to @autoreleasepool nigdy nie musi być używany i nie zrobi nic użytecznego. ARC zajmie się zwalnianiem obiektów w odpowiednim czasie. Na przykład:

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

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

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

Wyświetla:

p = 0x17696f80
dealloc
p = 0x17570a90
dealloc

Każdy obiekt testowy jest dealokowany tak szybko, jak to możliwe gdy wartość wykracza poza zakres, nie czekając na zakończenie puli autorelease. (To samo dzieje się z nsnumber przykład; to po prostu pozwala nam obserwować dealloc.) ARC nie używa autoreleazy.

Powodem, dla którego @autoreleasepool jest nadal dozwolony, jest mieszany projekt ARC i nie-Arc, który nie został jeszcze całkowicie przeniesiony do ARC.

Jeśli wywołasz kod nie-ARC, to może zwrócić obiekt z autorelementem. W takim przypadku powyższa pętla wyciekłaby, ponieważ bieżąca pula autorelease nigdy nie zostanie zamknięta. W tym miejscu powinieneś umieścić @autoreleasepool wokół bloku kodu.

Ale jeśli całkowicie dokonałeś przejścia łuku, zapomnij o autoreleasepool.

 -4
Author: Glenn Maynard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-24 22:26:51