Zrozumienie liczenia referencji za pomocą Cocoa i Objective-C

122

Właśnie zaczynam przyglądać się Objective-C i Cocoa z myślą o zabawie z iPhone SDK. Czuję się dość dobrze z C malloci freekoncepcją, ale schemat liczenia referencji Cocoa wprawia mnie w zakłopotanie. Powiedziano mi, że jest bardzo elegancki, kiedy to zrozumiesz, ale po prostu jeszcze nie przeszedłem.

Jak zrobić release, retaina autoreleasepraca i jakie są konwencje dotyczące ich stosowania?

(Lub w przypadku braku tego, co przeczytałeś, co pomogło ci to zdobyć?)

Matt Sheppard
źródło

Odpowiedzi:

148

Zacznijmy od retaini release; autoreleaseto naprawdę wyjątkowy przypadek, gdy zrozumiesz podstawowe pojęcia.

W kakao każdy obiekt śledzi, ile razy jest przywoływany (w szczególności NSObjectklasa bazowa implementuje to). Wzywając retainobiekt, mówisz mu, że chcesz zwiększyć jego liczbę odwołań o jeden. Dzwoniąc release, mówisz obiektowi, że go puszczasz, a jego liczba referencyjna jest zmniejszana. Jeśli po wywołaniu releaseliczba odwołań wynosi teraz zero, to pamięć tego obiektu jest zwalniana przez system.

Podstawowy sposób, w jaki to różni się od malloci freepolega na tym, że dany obiekt nie musi martwić się o awarię innych części systemu, ponieważ zwolniłeś pamięć, której używał. Zakładając, że wszyscy bawią się i zachowują / zwalniają zgodnie z regułami, gdy jeden fragment kodu zachowuje, a następnie zwalnia obiekt, żaden inny fragment kodu również odwołujący się do obiektu pozostanie niezmieniony.

Czasami mylące może być poznanie okoliczności, w których należy zadzwonić retaini release. Moja ogólna zasada jest taka, że ​​jeśli chcę trzymać się obiektu przez jakiś czas (na przykład jeśli jest to zmienna składowa w klasie), muszę się upewnić, że liczba odwołań do obiektu wie o mnie. Jak opisano powyżej, liczba odwołań do obiektu jest zwiększana przez wywołanie retain. Zgodnie z konwencją, jest on również zwiększany (naprawdę ustawiony na 1), gdy obiekt jest tworzony metodą „init”. W każdym z tych przypadków moim obowiązkiem jest wywołanie releaseobiektu, kiedy z nim skończę. Jeśli tego nie zrobię, nastąpi wyciek pamięci.

Przykład tworzenia obiektu:

NSString* s = [[NSString alloc] init];  // Ref count is 1
[s retain];                             // Ref count is 2 - silly
                                        //   to do this after init
[s release];                            // Ref count is back to 1
[s release];                            // Ref count is 0, object is freed

Teraz autorelease. Autorelease służy jako wygodny (i czasami konieczny) sposób nakazania systemowi zwolnienia tego obiektu po chwili. Z punktu widzenia hydrauliki, gdy autoreleasejest wywoływany, bieżący wątek NSAutoreleasePooljest powiadamiany o wywołaniu. NSAutoreleasePoolTeraz wie, że po to ma możliwość (po bieżącej iteracji pętli zdarzenia), to może zadzwonić releasena obiekcie. Z naszego punktu widzenia jako programistów dba o to, releaseaby nas wzywać , więc nie musimy (a właściwie nie powinniśmy).

Ważne jest, aby pamiętać, że (ponownie zgodnie z konwencją) wszystkie metody klas tworzenia obiektów zwracają obiekt z autouzyskiwania. Na przykład w poniższym przykładzie zmienna „s” ma liczbę odwołań równą 1, ale po zakończeniu pętli zdarzenia zostanie zniszczona.

NSString* s = [NSString stringWithString:@"Hello World"];

Jeśli chcesz zawiesić się na tym łańcuchu, musisz wywołać go retainjawnie, a następnie jawnie release, gdy skończysz.

Rozważ następujący (bardzo wymyślny) fragment kodu, a zobaczysz sytuację, w której autoreleasejest to wymagane:

- (NSString*)createHelloWorldString
{
    NSString* s = [[NSString alloc] initWithString:@"Hello World"];

    // Now what?  We want to return s, but we've upped its reference count.
    // The caller shouldn't be responsible for releasing it, since we're the
    // ones that created it.  If we call release, however, the reference 
    // count will hit zero and bad memory will be returned to the caller.  
    // The answer is to call autorelease before returning the string.  By 
    // explicitly calling autorelease, we pass the responsibility for
    // releasing the string on to the thread's NSAutoreleasePool, which will
    // happen at some later time.  The consequence is that the returned string 
    // will still be valid for the caller of this function.
    return [s autorelease];
}

Zdaję sobie sprawę, że to wszystko jest nieco zagmatwane - w pewnym momencie jednak kliknie. Oto kilka odniesień, które pomogą Ci zacząć:

  • Wprowadzenie firmy Apple do zarządzania pamięcią.
  • Cocoa Programming for Mac OS X (4th Edition) , autorstwa Aarona Hillegasa - bardzo dobrze napisana książka z wieloma świetnymi przykładami. Brzmi jak samouczek.
  • Jeśli naprawdę nurkujesz, możesz udać się na Big Nerd Ranch . To ośrodek szkoleniowy prowadzony przez Aarona Hillegasa - autora wspomnianej książki. Byłem tam kilka lat temu na kursie Intro to Cocoa i był to świetny sposób na naukę.
Matt Dillard
źródło
8
Napisałeś: „Dzwoniąc do autorelease, tymczasowo zwiększamy liczbę referencji”. Myślę, że to źle; autorelease oznacza tylko obiekt do wydania w przyszłości, nie zwiększa liczby ref: cocoadev.com/index.pl?AutoRelease
LKM
2
„Teraz czas na automatyczne wydanie. Autowydzielanie jest używane jako wygodny (a czasem konieczny) sposób nakazania systemowi zwolnienia tego obiektu po chwili”. Jako zdanie wprowadzające jest to błędne. Nie mówi systemowi, aby „zwolnił [go]”, mówi mu, aby zmniejszył liczbę zachowań.
mmalc
3
Wielkie dzięki za dobre wyjaśnienie. Tylko jedna rzecz, która jest nadal niejasna. Jeśli NSString* s = [[NSString alloc] initWithString:@"Hello World"];zwraca obiekt z autouzupełniania (tak jak go piszesz), dlaczego muszę zrobić a return [s autorelease];i ustawić go ponownie na „autorelease”, a nie tylko return s?
znq
3
@Stefan: [[NSString alloc] initWithString:@"Hello World"]NIE zwróci automatycznie udostępnianego obiektu. Za każdym razem, gdy allocjest wywoływana, liczba odwołań jest ustawiana na 1 i to kod jest odpowiedzialny za upewnienie się, że zostanie wydany. [NSString stringWithString:]Wezwanie, z drugiej strony, nie zwracają autoreleased obiekt.
Matt Dillard
6
Ciekawostki: Ponieważ odpowiedź używa @ "" i NSString, ciągi znaków są stałe, a zatem bezwzględna liczba zachowań będzie stała i całkowicie nieistotna .... nie oznacza, że ​​odpowiedź jest w żaden sposób błędna, po prostu wzmacnia fakt, że bezwzględna liczba zatrzymań nigdy nie jest czymś, o co należy się martwić.
bbum,
10

Jeśli rozumiesz proces zatrzymania / zwolnienia, istnieją dwie złote zasady, które są oczywiste dla doświadczonych programistów Cocoa, ale niestety rzadko są jasno określone dla nowicjuszy.

  1. Jeśli funkcja zwracająca obiekt ma alloc , createlub copyw jego imieniu, a następnie obiekt jest twoje. Musisz zadzwonić, [object release]kiedy skończysz. Lub CFRelease(object), jeśli jest to obiekt Core-Foundation.

  2. Jeśli NIE ma w nazwie żadnego z tych słów, to obiekt należy do kogoś innego. Musisz wywołać, [object retain]jeśli chcesz zachować obiekt po zakończeniu funkcji.

Dobrze by było, gdybyś przestrzegał tej konwencji w funkcjach, które sam tworzysz.

(Nitpickers: Tak, niestety istnieje kilka wywołań API, które są wyjątkami od tych reguł, ale są one rzadkie).

Andrew Grant
źródło
11
To jest niekompletne i niedokładne. Nadal nie rozumiem, dlaczego ludzie próbują powtarzać zasady, zamiast po prostu wskazywać odpowiednią dokumentację: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/ ...
mmalc
4
W szczególności zasady Core Foundation różnią się od zasad Cocoa; zobacz developer.apple.com/documentation/CoreFoundation/Conceptual/…
mmalc
1
Ja też się nie zgadzam. Jeśli funkcja zwraca coś, czego nie chce posiadać, powinna ją automatycznie zwolnić. To osoba wywołująca zadanie funkcji ma je zachować (w razie potrzeby). Nie powinno to mieć nic wspólnego z nazwą wywoływanej metody. To jest bardziej kodowanie w stylu C, w którym własność obiektów jest niejasna.
Sam
1
Przepraszam! Myślę, że pospiesznie głosowałem przeciw. Zasady zarządzania pamięcią Twoja odpowiedź prawie cytuje dokument Apple.
Sam
8

Jeśli piszesz kod dla pulpitu i możesz kierować reklamy na Mac OS X 10.5, powinieneś przynajmniej przyjrzeć się używaniu czyszczenia pamięci Objective-C. To naprawdę uprości większość twojego rozwoju - dlatego Apple włożyło cały wysiłek w stworzenie go w pierwszej kolejności i sprawienie, by działał dobrze.

Jeśli chodzi o zasady zarządzania pamięcią, gdy nie używasz GC:

  • W przypadku utworzenia nowego obiektu za pomocą +alloc/+allocWithZone:, +new, -copylub -mutableCopylub jeśli -retainobiekt, pacjent przyjmuje go na własność i należy upewnić się, że jest wysyłany -release.
  • Jeśli pojawi się obiekt w jakikolwiek inny sposób, jesteś nie właściciel i powinna nie zapewniają jego wysłaniem -release.
  • Jeśli chcesz mieć pewność, że obiekt zostanie wysłany -release, możesz wysłać go samodzielnie lub możesz wysłać obiekt, -autoreleasea bieżąca pula autorelease wyśle ​​go-release (raz na odebranie -autorelease), gdy pula zostanie opróżniona.

Zwykle -autoreleasejest używany jako sposób na zapewnienie, że obiekty będą żyły przez czas trwania bieżącego zdarzenia, ale są później czyszczone, ponieważ istnieje pula automatycznego uwalniania, która otacza przetwarzanie zdarzeń Cocoa. W kakao o wiele bardziej powszechne jest zwracanie obiektów wywołującego, które są automatycznie zwalniane, niż zwracanie obiektów, które sam wywołujący musi zwolnić.

Chris Hanson
źródło
6

Objective-C korzysta z liczenia referencji , co oznacza, że ​​każdy obiekt ma liczbę odwołań. Utworzony obiekt ma liczbę odwołań równą „1”. Mówiąc najprościej, gdy odwołuje się do obiektu (tj. Jest gdzieś przechowywany), zostaje on „zachowany”, co oznacza, że ​​jego liczba odwołań jest zwiększona o jeden. Gdy obiekt nie jest już potrzebny, jest „zwalniany”, co oznacza, że ​​jego liczba referencyjna jest zmniejszana o jeden.

Gdy liczba odwołań do obiektu wynosi 0, obiekt zostaje zwolniony. To jest podstawowe liczenie referencji.

W przypadku niektórych języków referencje są automatycznie zwiększane i zmniejszane, ale cel-c nie jest jednym z tych języków. Dlatego programista jest odpowiedzialny za przechowywanie i zwalnianie.

Typowy sposób pisania metody to:

id myVar = [someObject someMessage];
.... do something ....;
[myVar release];
return someValue;

Problem konieczności pamiętania o zwolnieniu wszelkich pozyskanych zasobów w kodzie jest zarówno żmudny, jak i podatny na błędy. Objective-C wprowadza kolejną koncepcję, która ma to znacznie ułatwić: pule Autorelease. Pule autorelease to specjalne obiekty instalowane w każdym wątku. Są dość prostą klasą, jeśli spojrzysz na NSAutoreleasePool.

Gdy do obiektu zostanie wysłana wiadomość „autorelease”, obiekt będzie szukał puli autorelease na stosie dla bieżącego wątku. Doda obiekt do listy jako obiekt, do którego ma zostać wysłany komunikat „o zwolnieniu” w pewnym momencie w przyszłości, czyli zwykle wtedy, gdy pula jest zwalniana.

Biorąc powyższy kod, możesz przepisać go na krótszy i łatwiejszy do odczytania, mówiąc:

id myVar = [[someObject someMessage] autorelease];
... do something ...;
return someValue;

Ponieważ obiekt jest zwolniony automatycznie, nie musimy już jawnie wywoływać na nim „wydania”. Dzieje się tak, ponieważ wiemy, że część automatycznej puli wydań zrobi to za nas później.

Mam nadzieję, że to pomoże. Artykuł w Wikipedii jest całkiem dobry na temat liczenia referencji. Więcej informacji o pulach autowyrejestrowań można znaleźć tutaj . Pamiętaj również, że jeśli tworzysz dla systemu Mac OS X 10.5 i nowszych, możesz powiedzieć Xcode, aby kompilował z włączoną funkcją czyszczenia pamięci, co pozwala całkowicie zignorować zachowanie / zwolnienie / autorelease.

NilObject
źródło
2
To jest po prostu złe. W żadnym z przedstawionych przykładów nie ma potrzeby wysyłania wydania niektórych obiektów lub autorlease.
mmalc
6

Joshua (# 6591) - Rzeczy do zbierania śmieci w systemie Mac OS X 10.5 wydają się całkiem fajne, ale nie są dostępne dla iPhone'a (lub jeśli chcesz, aby Twoja aplikacja działała na wersjach Mac OS X wcześniejszych niż 10.5).

Ponadto, jeśli piszesz bibliotekę lub coś, co może być ponownie użyte, korzystanie z trybu GC blokuje każdego używającego kodu również w trybie GC, więc jak rozumiem, każdy, kto próbuje napisać kod do wielokrotnego użytku, ma tendencję do zarządzania pamięć ręcznie.

Matt Sheppard
źródło
2
Jest całkowicie możliwe napisanie struktury hybrydowej, która obsługuje zarówno GC, jak i zliczanie referencji.
mmalc
6

Jak zawsze, kiedy ludzie zaczynają próbować przeformułować materiał referencyjny, prawie zawsze coś jest nie tak lub podają niepełny opis.

Firma Apple zapewnia pełny opis systemu zarządzania pamięcią Cocoa w Przewodniku programowania zarządzania pamięcią dla Cocoa , na końcu którego znajduje się krótkie, ale dokładne podsumowanie reguł zarządzania pamięcią .

mmalc
źródło
1
A dla zasad podsumowania: developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/…
Michael Baltaks
2
Właściwie jest to znacznie lepsze podsumowanie jednostronicowe: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
Brian Moeskau
6

Nie będę dodawać do szczegółów dotyczących zachowania / wydania innych niż myślisz o upuszczeniu 50 $ i zdobyciu książki Hillegass, ale zdecydowanie sugerowałbym rozpoczęcie korzystania z narzędzi Instruments na bardzo wczesnym etapie tworzenia aplikacji (nawet pierwszy!). Aby to zrobić, Uruchom-> Rozpocznij z narzędziami wydajności. Zacząłbym od Leaks, który jest tylko jednym z wielu dostępnych instrumentów, ale pomoże ci pokazać, kiedy zapomniałeś wydać. To, jak wiele informacji zostanie przedstawionych, przestaje zniechęcać. Ale zapoznaj się z tym samouczkiem, aby szybko wstać:
KAKAOWY TUTORIAL: USUWANIE WYCIEKÓW PAMIĘCI ZA POMOCĄ INSTRUMENTÓW

Właściwie próba wymuszenia wycieków może być z kolei lepszym sposobem nauczenia się, jak im zapobiegać! Powodzenia ;)

Obrabować
źródło
5

Matt Dillard napisał :

return [[s autorelease] release];

Autorelease tak nie zatrzymuje przedmiotu. Autorelease po prostu umieszcza go w kolejce do wydania później. Nie chcesz mieć tam oświadczenia o zwolnieniu.

NilObject
źródło
4

Odpowiedź NilObject to dobry początek. Oto dodatkowe informacje dotyczące ręcznego zarządzania pamięcią ( wymagane na iPhonie ).

Jeśli osobiście jesteś alloc/initobiektem, ma on liczbę referencyjną równą 1. Jesteś odpowiedzialny za sprzątanie po nim, gdy nie jest już potrzebny, dzwoniąc [foo release]lub[foo autorelease] . release czyści go od razu, podczas gdy autorelease dodaje obiekt do puli autorelease, która automatycznie zwolni go w późniejszym czasie.

autorelease jest przeznaczone głównie dla sytuacji, gdy masz metodę, która musi zwrócić dany obiekt ( więc nie możesz go ręcznie zwolnić, w przeciwnym razie zwrócisz obiekt zerowy ), ale nie chcesz też go zatrzymać .

Jeśli zdobędziesz obiekt, w którym nie wywołałeś funkcji assign / init, aby go pobrać - na przykład:

foo = [NSString stringWithString:@"hello"];

ale chcesz trzymać się tego obiektu, musisz wywołać [foo retain]. W przeciwnym razie możliwe, że tak się stanie autoreleasedi będziesz trzymać się zerowego odniesienia (tak jak w powyższym stringWithStringprzykładzie ). Kiedy już jej nie potrzebujesz, zadzwoń [foo release].

Mike McMaster
źródło
2

Powyższe odpowiedzi zawierają jasne powtórzenia tego, co mówi dokumentacja; problem, na który natrafia większość nowych ludzi, to przypadki nieudokumentowane. Na przykład:

  • Autorelease : dokumenty twierdzą, że spowoduje to wydanie „w pewnym momencie w przyszłości”. KIEDY?! Zasadniczo możesz liczyć na to, że obiekt będzie w pobliżu, dopóki nie wyjdziesz z kodu z powrotem do pętli zdarzeń systemowych. System MOŻE zwolnić obiekt w dowolnym momencie po bieżącym cyklu zdarzenia. (Myślę, że Matt powiedział to wcześniej.)

  • Ciągi statyczne : NSString *foo = @"bar";- czy musisz to zachować lub zwolnić? Nie. A może

    -(void)getBar {
        return @"bar";
    }

    ...

    NSString *foo = [self getBar]; // still no need to retain or release
  • Reguła tworzenia : jeśli ją stworzyłeś, jesteś jej właścicielem i oczekuje się, że ją opublikujesz.

Ogólnie rzecz biorąc, nowi programiści Cocoa popełniają błędy, ponieważ nie rozumieją, które procedury zwracają obiekt z rozszerzeniem retainCount > 0.

Oto fragment z bardzo prostych reguł zarządzania pamięcią w kakao :

Reguły licznika przechowywania

  • W ramach danego bloku użycie -copy, -alloc i -retain powinno równać się użyciu -release i -autorelease.
  • Obiekty utworzone za pomocą wygodnych konstruktorów (np. StringWithString firmy NSString) są uznawane za autowyznawane.
  • Zaimplementuj metodę -dealloc, aby zwolnić posiadane zmienne instancji

Punkt pierwszy mówi: jeśli zadzwoniłeś alloc(lub new fooCopy), musisz wywołać zwolnienie na tym obiekcie.

Drugi punktor mówi: jeśli używasz wygodnego konstruktora i chcesz, aby obiekt kręcił się w pobliżu (jak w przypadku obrazu do narysowania później), musisz go zachować (a następnie zwolnić).

Trzecia powinna być oczywista.

Olie
źródło
„Autorelease: doktorzy mówią, że spowoduje to wydanie” w pewnym momencie w przyszłości. „KIEDY ?!” Dokumenty są jasne w tym punkcie: „autorelease oznacza po prostu„ wyślij wiadomość o wersji później ”(aby zapoznać się z definicją później - zobacz„ Pule autorelease ”).” Dokładnie, kiedy zależy od stosu puli autowydzielania ...
mmalc
... "System MOŻE zwolnić obiekt w dowolnym momencie po bieżącym cyklu zdarzenia." To sprawia, że ​​system brzmi raczej mniej deterministycznie niż jest ...
mmalc
... NSString foo = [self getBar]; // nadal nie ma potrzeby zatrzymywania ani zwalniania To jest złe. Ktokolwiek wywołuje getBar, nie zna szczegółów implementacji, więc * powinien zachować / zwolnić (zazwyczaj przez metody dostępu ), jeśli chce używać go poza bieżącym zakresem.
mmalc
Artykuł „Bardzo proste zasady zarządzania pamięcią w kakao” jest pod kilkoma względami nieaktualny - w szczególności „Obiekty utworzone za pomocą wygodnych konstruktorów (np. StringWithString firmy NSString) są uważane za autoreleased”. nie jest w porządku - po prostu „nie jest własnością odbiorcy”.
mmalc
0

Jak już kilka osób wspomniało, Apple Intro to Memory Management jest zdecydowanie najlepszym miejscem do rozpoczęcia.

Jednym z przydatnych linków, o których jeszcze nie widziałem, jest Praktyczne zarządzanie pamięcią . Znajdziesz go w środku dokumentów Apple, jeśli je przeczytasz, ale warto to zrobić bezpośrednio. Jest to genialne podsumowanie reguł zarządzania pamięcią z przykładami i typowymi błędami (w zasadzie to, co próbują wyjaśnić inne odpowiedzi, ale nie tak dobrze).

Brian Moeskau
źródło