Dlaczego @autoreleasepool jest nadal potrzebny z ARC?

191

W przeważającej części dzięki ARC (automatyczne zliczanie referencji) nie musimy w ogóle myśleć o zarządzaniu pamięcią w przypadku obiektów Objective-C. Nie można już tworzyć NSAutoreleasePools, jednak istnieje nowa składnia:

@autoreleasepool {
    
}

Moje pytanie brzmi: dlaczego miałbym tego potrzebować, skoro nie powinienem ręcznie zwalniać / automatycznie zwalniać?


EDYCJA: Podsumowując to, co wyciągnąłem ze wszystkich odpowiedzi i komentarzy zwięźle:

Nowa składnia:

@autoreleasepool { … } to nowa składnia dla

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

[pool drain];

Co ważniejsze:

  • ARC wykorzystuje autoreleaserównież release.
  • Potrzebuje do tego automatycznej puli zwolnień.
  • ARC nie tworzy dla ciebie automatycznej puli zwolnień. Jednak:
    • W głównym wątku każdej aplikacji Cocoa jest już pula autorelease.
  • Istnieją dwie sytuacje, w których warto skorzystać z @autoreleasepool:
    1. Gdy jesteś w wątku dodatkowym i nie ma puli automatycznego zwalniania, musisz zrobić własną, aby zapobiec wyciekom, np myRunLoop(…) { @autoreleasepool { … } return success; } .
    2. Gdy chcesz utworzyć bardziej lokalną pulę, jak pokazał @mattjgalloway w swojej odpowiedzi.
mk12
źródło
1
Jest też trzecia okazja: kiedy opracujesz coś, co nie jest związane z UIKit ani NSFoundation. Coś, co używa mniej więcej narzędzi wiersza poleceń
Garnik,

Odpowiedzi:

215

ARC nie pozbywa się zatrzymań, wydań i automatycznych wydań, po prostu dodaje wymagane dla ciebie. Tak więc wciąż są wezwania do zachowania, wciąż są wezwania do wydania, wciąż są wezwania do automatycznego wydawania i wciąż istnieją pule automatycznego zwolnienia.

Jedną z innymi zmianami one wykonane z nowym Clang 3.0 kompilatora i ARC jest to, że otrzymuje NSAutoReleasePoolz @autoreleasepooldyrektywy kompilatora.NSAutoReleasePooli tak zawsze był trochę specjalnym „przedmiotem” i sprawili, że składnia użycia jednego nie jest mylona z obiektem, więc ogólnie jest nieco prostsza.

Zasadniczo potrzebujesz, @autoreleasepoolponieważ nadal istnieją martwe pule automatycznych wersji. Po prostu nie musisz się martwić dodawaniem autoreleasepołączeń.

Przykład użycia automatycznej puli 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);
            }
        }
    }
}

Ogromnie wymyślony przykład, oczywiście, ale jeśli nie miałbyś @autoreleasepoolwewnętrznej forpętli zewnętrznej , to później wydawałbyś 100000000 obiektów, a nie 10000 za każdym razem wokół zewnętrznej forpętli.

Aktualizacja: Zobacz także tę odpowiedź - https://stackoverflow.com/a/7950636/1068248 - dlaczego @autoreleasepoolnie ma to nic wspólnego z ARC.

Aktualizacja: przyjrzałem się temu, co się tutaj dzieje, i napisałem to na moim blogu . Jeśli się tam przyjrzysz, zobaczysz dokładnie, co robi ARC i jak nowy styl @autoreleasepooli sposób wprowadzenia zakresu jest używany przez kompilator do wnioskowania o tym, jakie zachowania, wydania i automatyczne wydania są wymagane.

mattjgalloway
źródło
11
Nie pozbywa się resztek. Dodaje je dla Ciebie. Liczenie referencji wciąż trwa, jest to po prostu automatyczne. Stąd automatyczne liczenie referencji :-D.
mattjgalloway
6
Dlaczego więc nie dodaje się @autoreleasepooldo mnie? Jeśli nie kontroluję, co zostanie automatycznie wydane lub wydane (ARC robi to dla mnie), to skąd mam wiedzieć, kiedy skonfigurować pulę autorelease?
mk12
5
Ale masz kontrolę nad tym, gdzie wciąż idą pule automatycznych zwolnień. Domyślnie jest on zawinięty wokół całej aplikacji, ale możesz chcieć więcej.
mattjgalloway
5
Dobre pytanie. Musisz tylko „wiedzieć”. Pomyśl o dodaniu jednego jako podobnego do tego, dlaczego w języku GC można dodać wskazówkę do śmieciarza, aby przejść dalej i uruchomić teraz cykl zbierania. Być może wiesz, że istnieje mnóstwo obiektów gotowych do usunięcia, masz pętlę, która przydziela kilka obiektów tymczasowych, więc „wiesz” (lub Instruments może ci powiedzieć :), że dodanie puli wersji wokół pętli byłoby dobry pomysł.
Graham Perks,
6
Przykład zapętlenia działa idealnie bez autorelease: każdy obiekt jest zwalniany, gdy zmienna wykracza poza zakres. Uruchomienie kodu bez automatycznego uwalniania wymaga stałej ilości pamięci i pokazuje ponowne użycie wskaźników, a umieszczenie punktu przerwania w dealloc obiektu pokazuje, że jest on wywoływany za każdym razem przez pętlę, gdy wywoływany jest obiekt objc_storeStrong. Może OSX robi tutaj coś głupiego, ale autoreleasepool jest zupełnie niepotrzebny na iOS.
Glenn Maynard
16

@autoreleasepoolnic nie wydaje automatycznie. Tworzy pulę automatycznego uwalniania, dzięki czemu po osiągnięciu końca bloku wszystkie obiekty, które zostały automatycznie wydane przez ARC, gdy blok był aktywny, będą wysyłane komunikaty o zwolnieniu. Przewodnik programowania zaawansowanego zarządzania pamięcią firmy Apple wyjaśnia to w ten sposób:

Na końcu bloku puli automatycznego uwalniania do obiektów, które otrzymały komunikat automatycznego uwalniania w bloku, wysyłany jest komunikat zwolnienia - obiekt otrzymuje komunikat zwolnienia za każdym razem, gdy został wysłany komunikat zwolnienia automatycznego w bloku.

outis
źródło
1
Niekoniecznie. Obiekt otrzyma releasekomunikat, ale jeśli liczba zatrzymań wynosi> 1, obiekt NIE zostanie zwolniony.
andybons
@andybons: zaktualizowano; dzięki. Czy to zmiana w stosunku do zachowania sprzed ARC?
poza
To jest niepoprawne. Obiekty wydane przez ARC będą wysyłane wiadomości o wydaniu, gdy tylko zostaną zwolnione przez ARC, z pulą autorelease lub bez.
Glenn Maynard
7

Ludzie często źle rozumieją ARC dla jakiegoś rodzaju śmieci lub tym podobnych. Prawda jest taka, że ​​po pewnym czasie ludzie w Apple (dzięki projektom llvm i clang) zdali sobie sprawę, że administracja pamięcią Objective-C (wszystko retainsi releasesitp.) Może być w pełni zautomatyzowana w czasie kompilacji . Jest to po prostu czytanie kodu, nawet przed jego uruchomieniem! :)

Aby to zrobić, jest tylko jeden warunek: MUSIMY przestrzegać reguł , w przeciwnym razie kompilator nie byłby w stanie zautomatyzować procesu w czasie kompilacji. Tak, aby zapewnić, że nigdy nie łamać zasady, nie wolno pisać wyraźnie release, retainitp Połączenia te są automatycznie wstrzykuje się do naszego kodu przez kompilator. Stąd wewnętrznie wciąż mamy autoreleases, retain,release , itd. To jest po prostu nie musimy pisać ich więcej.

A z ARC jest automatyczne w czasie kompilacji, co jest znacznie lepsze niż w czasie wykonywania, takie jak odśmiecanie.

Nadal mamy, @autoreleasepool{...}ponieważ mając go nie łamie żadnych zasad, możemy swobodnie tworzyć / opróżniać naszą pulę, kiedy tylko potrzebujemy :).

nacho4d
źródło
1
ARC to liczenie referencji GC, nie GC mark-and-sweep, jak w JavaScript i Javie, ale to zdecydowanie śmieci. To nie dotyczy pytania - „możesz” nie odpowiada na pytanie „dlaczego powinieneś”. Nie powinieneś
Glenn Maynard
3

Dzieje się tak, ponieważ nadal musisz przekazać kompilatorowi wskazówki, kiedy bezpiecznie wydane obiekty wykraczają poza zakres.

DougW
źródło
Czy możesz podać mi przykład, kiedy musisz to zrobić?
mk12
Na przykład przed ARC miałem CVDisplayLink działający w wątku dodatkowym dla mojej aplikacji OpenGL, ale nie utworzyłem puli autorelease w jej runloopie, ponieważ wiedziałem, że niczego nie odsprzedajemy (ani nie używam bibliotek, które to robią). Czy to znaczy, że muszę teraz dodać, @autoreleasepoolponieważ nie wiem, czy ARC może zdecydować się na automatyczne wydanie czegoś?
mk12
@ Mk12 - Nie. Zawsze będziesz mieć pulę automatycznego zwalniania, która jest opróżniana za każdym razem wokół głównej pętli uruchamiania. Musisz dodać tylko jeden, jeśli chcesz mieć pewność, że obiekty, które zostały automatycznie wydane, zostaną opróżnione, zanim w przeciwnym razie - na przykład, następnym razem w pętli uruchamiania.
mattjgalloway
2
@DougW - przyjrzałem się temu, co faktycznie robi kompilator i blogowałem o tym tutaj - iphone.galloway.me.uk/2012/02/a-look-under-arcs-hood- –-episode-3 /. Mam nadzieję, że wyjaśnia, co się dzieje zarówno w czasie kompilacji, jak i w czasie wykonywania.
mattjgalloway
2

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

Bloki puli i wątki automatycznego wydawania

Każdy wątek w aplikacji Cocoa utrzymuje własny stos bloków puli autorelease. Jeśli piszesz program tylko dla Fundacji lub odłączysz wątek, musisz utworzyć własny blok puli autorelease.

Jeśli twoja aplikacja lub wątek jest długowieczny i potencjalnie generuje wiele automatycznie wydanych obiektów, powinieneś użyć bloków puli autorelease (takich jak AppKit i UIKit w głównym wątku); w przeciwnym razie automatycznie wydane obiekty gromadzą się, a ślad pamięci rośnie. Jeśli oderwany wątek nie wykonuje wywołań Cocoa, nie musisz używać bloku puli automatycznego wydawania.

Uwaga: W przypadku tworzenia wątków wtórnych za pomocą interfejsów API wątków POSIX zamiast NSThread nie można używać kakao, chyba że kakao jest w trybie wielowątkowym. Kakao przechodzi w tryb wielowątkowości dopiero po odłączeniu pierwszego obiektu NSThread. Aby użyć kakao w wtórnych wątkach POSIX, aplikacja musi najpierw odłączyć co najmniej jeden obiekt NSThread, który może natychmiast wyjść. Możesz sprawdzić, czy kakao jest w trybie wielowątkowym, stosując metodę klasy NSThread isMultiThreaded.

...

W automatycznym zliczaniu referencji (ARC) system korzysta z tego samego systemu zliczania referencji, co MRR, ale wstawia odpowiednią metodę zarządzania pamięcią w czasie kompilacji. Zachęcamy do korzystania z ARC w nowych projektach. Jeśli korzystasz z ARC, zazwyczaj nie ma potrzeby zrozumienia podstawowej implementacji opisanej w tym dokumencie, chociaż w niektórych sytuacjach może to być pomocne. Aby uzyskać więcej informacji na temat ARC, zobacz Przejście do informacji o wersji ARC.

Raunak
źródło
2

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

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

Ciąg utworzony w metodzie będzie miał liczbę zatrzymań równą jeden. Teraz, kto zrównoważy liczbę zatrzymań z uwolnieniem?

Sama metoda? Niemożliwe, musi zwrócić utworzony obiekt, więc nie może go zwolnić przed zwróceniem.

Program wywołujący metodę? Program wywołujący nie spodziewa się pobrać obiektu, który wymaga zwolnienia, nazwa metody nie oznacza, że ​​nowy obiekt jest tworzony, mówi tylko, że obiekt został zwrócony i ten zwrócony obiekt może być nowym wymagającym wydania, ale może bądź istniejącym, który nie. To, co metoda zwraca, może nawet zależeć od pewnego stanu wewnętrznego, więc osoba dzwoniąca nie może wiedzieć, czy musi zwolnić ten obiekt i nie powinna się tym przejmować.

Gdyby osoba dzwoniąca musiała zawsze zwalniać cały zwrócony obiekt zgodnie z konwencją, wówczas każdy obiekt, który nie został nowo utworzony, musiałby zawsze zostać zachowany przed zwróceniem go z metody i musiałby zostać zwolniony przez osobę dzwoniącą, gdy wykroczy poza zakres, chyba że jest zwracany ponownie. Byłoby to wysoce nieefektywne w wielu przypadkach, ponieważ można całkowicie uniknąć zmiany liczby zatrzymań w wielu przypadkach, jeśli dzwoniący nie zawsze zwolni zwrócony obiekt.

Właśnie dlatego istnieją pule autorelease, więc pierwsza metoda faktycznie się stanie

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

Wywołanie autoreleaseobiektu dodaje go do puli autorelease, ale co to tak naprawdę znaczy, dodając obiekt do puli autorelease? Cóż, oznacza to powiedzenie systemowi: „ Chcę, żebyś wypuścił ten obiekt dla mnie, ale później, nie teraz; ma liczbę zatrzymań, która musi być zrównoważona przez wydanie, inaczej pamięć wycieknie, ale nie mogę tego zrobić sam w tej chwili, ponieważ potrzebuję obiektu, aby pozostał przy życiu poza moim obecnym zasięgiem, a mój rozmówca też tego nie zrobi dla mnie, nie ma on wiedzy, że należy to zrobić. Więc dodaj go do swojej puli, a kiedy to wyczyścisz basen, posprzątaj też mój obiekt dla mnie ”.

Dzięki ARC kompilator decyduje o tym, kiedy zachować obiekt, kiedy zwolnić obiekt, a kiedy dodać go do puli autorelease, ale nadal wymaga obecności pul autorelease, aby móc zwrócić nowo utworzone obiekty z metod bez wycieku pamięci. Firma Apple właśnie dokonała sprytnych optymalizacji generowanego kodu, które czasami eliminują pule autorelease w czasie wykonywania. Te optymalizacje wymagają, aby zarówno dzwoniący, jak i odbierający korzystali z ARC (pamiętaj, że mieszanie ARC i non-ARC jest legalne i również oficjalnie obsługiwane), a jeśli tak, to w rzeczywistości można to zrobić tylko w czasie wykonywania.

Rozważ ten kod ARC:

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

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

Kod generowany przez system może zachowywać się podobnie do następującego kodu (jest to bezpieczna wersja, która pozwala swobodnie mieszać kod ARC i kod inny niż ARC):

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

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

(Uwaga: zatrzymanie / zwolnienie w dzwoniącym jest tylko obronnym zachowaniem bezpieczeństwa, nie jest bezwzględnie wymagane, bez niego kod byłby całkowicie poprawny)

Lub może zachowywać się tak, jak ten kod, na wypadek, gdyby oba wykryły użycie 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 także opóźnione uwalnianie obiektu po zniszczeniu puli, a także zachowanie bezpieczeństwa. Aby dowiedzieć się więcej o tym, jak to jest możliwe i co naprawdę dzieje się za kulisami, sprawdź ten post na blogu.

Przejdźmy teraz do właściwego pytania: po co używać @autoreleasepool?

W przypadku większości programistów pozostał tylko jeden powód, aby używać tego konstruktu w kodzie, a mianowicie zachować niewielką powierzchnię pamięci, jeśli ma to zastosowanie. Np. Rozważ tę pętlę:

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

Załóż, że każde połączenie do tempObjectForDatamoże utworzyć noweTempObject która jest zwracana automatycznie. Pętla for utworzy milion takich obiektów tymczasowych, które zostaną zebrane w bieżącej puli autorelease, a tylko po zniszczeniu tej puli wszystkie obiekty tymczasowe zostaną zniszczone. Do tego czasu w pamięci znajduje się milion takich obiektów tymczasowych.

Jeśli zamiast tego napiszesz taki kod:

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

Następnie nowa pula jest tworzona przy każdym uruchomieniu pętli for i jest niszczona na końcu każdej iteracji pętli. W ten sposób w pamięci może wisieć co najwyżej jeden obiekt tymczasowy, mimo że pętla działa milion razy.

W przeszłości często musiałeś samodzielnie zarządzać pulami autorelease podczas zarządzania wątkami (np. Za pomocą NSThread), ponieważ tylko główny wątek automatycznie ma pulę autorelease dla aplikacji Cocoa / UIKit. Ale jest to dziś dość spuścizna, ponieważ dziś prawdopodobnie nie używałbyś wątków na początek. Używałbyś GCD DispatchQueuelub NSOperationQueuetych, a ci dwaj zarządzają dla ciebie pulą autorelease najwyższego poziomu, utworzoną przed uruchomieniem bloku / zadania i zniszczoną, gdy już to zrobisz.

Mecki
źródło
-4

Wydaje się, że istnieje wiele nieporozumień na ten temat (i co najmniej 80 osób, które prawdopodobnie są teraz zdezorientowane w tym zakresie i myślą, że muszą posypać @autoreleasepool wokół swojego kodu).

Jeśli projekt (w tym jego zależności) używa wyłącznie ARC, @autoreleasepool nigdy nie musi być używany i nie zrobi nic użytecznego. ARC zajmie się wypuszczaniem obiektów we właściwym 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 zwalniany, gdy tylko wartość wykracza poza zakres, bez oczekiwania na wyjście z puli autorelease. (To samo dzieje się z przykładem NSNumber; pozwala nam to tylko obserwować dealloc.) ARC nie używa automatycznego uwalniania.

Powód @autoreleasepool jest nadal dozwolony z powodu mieszanych projektów ARC i innych niż ARC, które nie zostały jeszcze całkowicie przeniesione do ARC.

Jeśli wywołasz kod spoza ARC, może on zwrócić automatycznie wydany obiekt. W takim przypadku powyższa pętla wyciekłaby, ponieważ bieżąca pula autorelease nigdy nie zostanie zamknięta. Właśnie tam chcesz umieścić @autoreleasepool wokół bloku kodu.

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

Glenn Maynard
źródło
4
Ta odpowiedź jest błędna i jest również sprzeczna z dokumentacją ARC. twoje dowody są niepotwierdzone, ponieważ akurat używasz metody alokacji, której kompilator decyduje się nie uruchamiać automatycznie. Możesz bardzo łatwo zobaczyć, że to nie działa, jeśli utworzysz nowy statyczny inicjator dla swojej klasy niestandardowej. Tworzenie tego inicjatora i używać go w pętli: + (Testing *) testing { return [Testing new] }. Wtedy zobaczysz, że dealloc nie zostanie wezwany do później. Jest to naprawione, jeśli owiniesz wnętrze pętli @autoreleasepoolblokiem.
Dima
@Dima Próbowałem na iOS10, dealloc zostaje wywołany natychmiast po wydrukowaniu adresu obiektu. + (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
KudoCC
@KudoCC - Ja też widziałem to samo zachowanie, co ty. Ale kiedy rzuciłem [UIImage imageWithData]się na równanie, nagle zacząłem dostrzegać tradycyjne autoreleasezachowanie, wymagające @autoreleasepoolutrzymania szczytowej pamięci na rozsądnym poziomie.
Rob
@Rob Nie mogę się powstrzymać przed dodaniem linku .
KudoCC