Jak działa pula autorelease NSAutoreleasePool?

95

Jak rozumiem, wszystko, co zostało utworzone za pomocą alokacji , nowego lub kopii, musi zostać ręcznie zwolnione. Na przykład:

int main(void) {
    NSString *string;
    string = [[NSString alloc] init];
    /* use the string */
    [string release];
}

Moje pytanie brzmi jednak, czy nie byłoby to równie ważne ?:

int main(void) {
    NSAutoreleasePool *pool;
    pool = [[NSAutoreleasePool alloc] init];
    NSString *string;
    string = [[[NSString alloc] init] autorelease];
    /* use the string */
    [pool drain];
}
James Sumners
źródło

Odpowiedzi:

68

Tak, Twój drugi fragment kodu jest całkowicie prawidłowy.

Za każdym razem, gdy -autorelease są wysyłane do obiektu, jest dodawane do najbardziej wewnętrznej puli autorelease. Gdy basen jest opróżniony, po prostu wysyła -release do wszystkich obiektów w basenie.

Pule autowydzielania to po prostu wygoda, która pozwala na odroczenie wysyłania i zwalniania do „później”. To „później” może się zdarzyć w kilku miejscach, ale najczęściej w aplikacjach GUI Cocoa występuje na końcu bieżącego cyklu pętli uruchamiania.

kperryua
źródło
5
gdzie jest koniec bieżącego cyklu run pętli, jeśli nie mam pętli?
Dzięki
24
Czy „najbardziej zewnętrzna” nie powinna być „najbardziej wewnętrzna”?
Mike Weller
an objectpowinno być an object that is a subclass of NSObject or NSProxy and doesn't override -autorelease.
1
EDYCJA: zmieniono najbardziej zewnętrzną na najbardziej wewnętrzną.
chakrit
1
Ważne: W przypadku korzystania z automatycznego zliczania odwołań (ARC) nie można bezpośrednio korzystać z pul automatycznego zwalniania. Zamiast tego używasz bloków @autoreleasepool. Od developer.apple.com/library/mac/#documentation/Cocoa/Reference/…
Md Mahbubur Rahman
37

NSAutoreleasePool: dren vs. release

Ponieważ funkcja draini releasewydaje się powodować zamieszanie, warto tutaj wyjaśnić (chociaż jest to omówione w dokumentacji ...).

Ściśle mówiąc, z szerszej perspektywy niedrain jest to równoznaczne z release:

W środowisku zliczanym referencjami drainwykonuje te same operacje, co release, więc oba są w tym sensie równoważne. Aby podkreślić, oznacza to, że nie przeciekasz basenu, jeśli używasz drainzamiast release.

W środowisku zbierania śmieci releasenie ma możliwości. Zatem nie ma to żadnego skutku. drainz drugiej strony zawiera wskazówkę dla zbieracza, że ​​powinien „zbierać w razie potrzeby”. Tak więc w środowisku zbierania śmieci używanie drainpomaga systemowi zrównoważyć proces zbierania danych.

mmalc
źródło
4
Zasadniczo niemożliwe jest „wyciekanie” pliku NSAutoreleasePool. Dzieje się tak, ponieważ pule działają jak stos. Utworzenie wystąpienia puli powoduje wypychanie tej puli na szczyt stosu puli autowyrejestrowywania wątków. -releasepowoduje, że pula ta wyskakuje ze stosu ORAZ wszystkie pule, które zostały na nią odłożone, ale z jakiegoś powodu nie zostały zdeponowane.
johne
7
W jakim sensie ma to związek z tym, co napisałem?
mmalc
2
Podoba mi się, jak poświęcił czas na odważne i. KŁAPNIĘCIE!
Billy Gray
17

Jak już wspomniano, drugi fragment kodu jest poprawny.

Chciałbym zasugerować bardziej zwięzły sposób korzystania z puli autowydzielania, która działa we wszystkich środowiskach (liczenie referencji, GC, ARC), a także pozwala uniknąć zamieszania:

int main(void) {
  @autoreleasepool {
    NSString *string;
    string = [[[NSString alloc] init] autorelease];
    /* use the string */
  }
}

W powyższym przykładzie zwróć uwagę na blok @autoreleasepool . Jest to udokumentowane tutaj .

Neovibrant
źródło
2
Należy pamiętać, że automatyczne udostępnianie nie jest dozwolone w przypadku ARC.
dmirkitanov
1
Aby wyjaśnić, należy użyć @autoreleasepoolbloku z ARC.
Simon
7

Nie, mylisz się. Dokumentacja jasno stwierdza, że ​​w trybie non-GC -drain jest równoważne -release, co oznacza, że ​​pakiet NSAutoreleasePool nie zostanie ujawniony.

kperryua
źródło
Zastanawiałem się, dlaczego Xcode generowałby kod z -drain, gdyby tak było. Użyłem -drain, ponieważ myślałem, że jest to odpowiednik -release na podstawie kodu wygenerowanego przez Xcode.
James Sumners,
1
Zasadniczo niemożliwe jest „wyciek” a NSAutoreleasePool: developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…
johne
0

wysłanie autorelease zamiast zwolnienia do obiektu wydłuża żywotność tego obiektu przynajmniej do momentu opróżnienia samej puli (może to być dłuższe, jeśli obiekt zostanie później zachowany). Obiekt można umieścić w tej samej puli kilka razy, w takim przypadku otrzymuje komunikat o zwolnieniu za każdym razem, gdy został umieszczony w puli.

Hardik Mamtora
źródło
-2

Tak i nie. Skończyło się na zwolnieniu pamięci ciągu, ale „wyciekaniu” obiektu NSAutoreleasePool do pamięci przez użycie drenu zamiast zwolnienia, jeśli uruchomiono to w środowisku zbierania elementów bezużytecznych (bez zarządzania pamięcią). Ten „wyciek” po prostu sprawia, że ​​wystąpienie NSAutoreleasePool jest „nieosiągalne”, jak każdy inny obiekt bez silnych wskaźników w GC, a obiekt zostałby wyczyszczony przy następnym uruchomieniu GC, co może być bezpośrednio po wywołaniu -drain:

drenaż

W środowisku wyrzucania elementów bezużytecznych wyzwala wyrzucanie elementów bezużytecznych, jeśli pamięć przydzielona od ostatniej kolekcji jest większa niż bieżący próg; w przeciwnym razie zachowuje się jak zwolnienie. ... W środowisku ze śmieciami ta metoda ostatecznie wywołuje objc_collect_if_needed.

W przeciwnym razie jest to podobne do tego, jak -releasezachowuje się pod nie-GC, tak. Jak powiedzieli inni, nie -releasedziała w GC, więc jedynym sposobem, aby upewnić się, że pula działa poprawnie w GC, jest przejście -drain, a -drainpod non-GC działa dokładnie tak, jak -releasew przypadku innego niż GC i prawdopodobnie lepiej komunikuje swoją funkcjonalność, ponieważ dobrze.

Powinienem zwrócić uwagę, że twoje oświadczenie "wszystko wywołane z new, przydziel lub init" nie powinno zawierać "init" (ale powinno zawierać "copy"), ponieważ "init" nie alokuje pamięci, a jedynie ustawia obiekt (konstruktor moda). Jeśli otrzymałeś przydzielony obiekt, a twoja funkcja wywołałaby tylko init jako taką, nie zwolniłbyś jej:

- (void)func:(NSObject*)allocd_but_not_init
{
    [allocd_but_not_init init];
}

To nie zużywa więcej pamięci niż ta, od której już zacząłeś (zakładając, że init nie tworzy instancji obiektów, ale i tak nie jesteś za nie odpowiedzialny).

Loren Segal
źródło
Nie czuję się komfortowo, pozostawiając tę ​​odpowiedź jako zaakceptowaną, gdy Twoje informacje na temat drenażu są nieprawidłowe. Zobacz developer.apple.com/documentation/Cocoa/Reference/Foundation/ ... Zaktualizuj, a ja ponownie zaakceptuję.
James Sumners,
Co jest niedokładne w odpowiedzi? W środowisku zbierania elementów bezużytecznych (jak wspomniano) drenowanie nie usuwa AutoReleasePool, więc nastąpi wyciek pamięci, chyba że użyłeś wydania. Cytat, który wymieniłem, pochodził prosto z pyska konia, lekarze na drenażu.
Loren Segal
1
Loren: W GC, - [NSAutoreleasePool drain] uruchomi kolekcję. -retain, -release i -autorelease są ignorowane przez kolekcjonera; dlatego -drain jest używane w pulach autorelease pod GC.
Chris Hanson,
W dokumentacji dotyczącej „drenu”: w zarządzanym środowisku pamięci zachowuje się to tak samo, jak wywołanie wydania. W ten sposób będzie nie wyciek pamięci, jeśli używasz „drenażu” zamiast uwalniania.
mmalc
-[NSAutoreleasePool release]w środowisku, w którym zbierane są elementy bezużyteczne. -[NSAutoreleasePool drain]działa zarówno w środowiskach zliczanych odwołań, jak i ze śmieciami.
Jonathan Sterling