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ć NSAutoreleasePool
s, 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
autorelease
ró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
:- Gdy jesteś w wątku dodatkowym i nie ma puli automatycznego zwalniania, musisz zrobić własną, aby zapobiec wyciekom, np
myRunLoop(…) { @autoreleasepool { … } return success; }
. - Gdy chcesz utworzyć bardziej lokalną pulę, jak pokazał @mattjgalloway w swojej odpowiedzi.
- Gdy jesteś w wątku dodatkowym i nie ma puli automatycznego zwalniania, musisz zrobić własną, aby zapobiec wyciekom, np
Odpowiedzi:
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
NSAutoReleasePool
z@autoreleasepool
dyrektywy kompilatora.NSAutoReleasePool
i 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,
@autoreleasepool
ponieważ nadal istnieją martwe pule automatycznych wersji. Po prostu nie musisz się martwić dodawaniemautorelease
połączeń.Przykład użycia automatycznej puli zwolnień:
Ogromnie wymyślony przykład, oczywiście, ale jeśli nie miałbyś
@autoreleasepool
wewnętrznejfor
pętli zewnętrznej , to później wydawałbyś 100000000 obiektów, a nie 10000 za każdym razem wokół zewnętrznejfor
pętli.Aktualizacja: Zobacz także tę odpowiedź - https://stackoverflow.com/a/7950636/1068248 - dlaczego
@autoreleasepool
nie 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
@autoreleasepool
i sposób wprowadzenia zakresu jest używany przez kompilator do wnioskowania o tym, jakie zachowania, wydania i automatyczne wydania są wymagane.źródło
@autoreleasepool
do 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?@autoreleasepool
nic 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:źródło
release
komunikat, ale jeśli liczba zatrzymań wynosi> 1, obiekt NIE zostanie zwolniony.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
retains
ireleases
itp.) 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
,retain
itp Połączenia te są automatycznie wstrzykuje się do naszego kodu przez kompilator. Stąd wewnętrznie wciąż mamyautorelease
s,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 :).źródło
Dzieje się tak, ponieważ nadal musisz przekazać kompilatorowi wskazówki, kiedy bezpiecznie wydane obiekty wykraczają poza zakres.
źródło
@autoreleasepool
ponieważ nie wiem, czy ARC może zdecydować się na automatyczne wydanie czegoś?Cytat z https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html :
...
źródło
Pule automatycznego wydawania są wymagane do zwracania nowo utworzonych obiektów z metody. Np. Rozważ ten fragment kodu:
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
Wywołanie
autorelease
obiektu 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:
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):
(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:
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ę:
Załóż, że każde połączenie do
tempObjectForData
moż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:
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ś GCDDispatchQueue
lubNSOperationQueue
tych, a ci dwaj zarządzają dla ciebie pulą autorelease najwyższego poziomu, utworzoną przed uruchomieniem bloku / zadania i zniszczoną, gdy już to zrobisz.źródło
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:
wyświetla:
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.
źródło
+ (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@autoreleasepool
blokiem.+ (Testing *) testing { return [Testing new];} + (void) test { while(true) NSLog(@"p = %p", [self testing]);}
[UIImage imageWithData]
się na równanie, nagle zacząłem dostrzegać tradycyjneautorelease
zachowanie, wymagające@autoreleasepool
utrzymania szczytowej pamięci na rozsądnym poziomie.