Dlaczego delegatom celu-C zwykle przypisuje się właściwość zamiast zachować?

176

Przeglądam wspaniały blog prowadzony przez Scotta Stevensona i próbuję zrozumieć podstawową koncepcję Objective-C polegającą na przypisywaniu delegatom właściwości „przypisuj” zamiast „zachowaj”. Uwaga: oba są takie same w środowisku zbierania elementów bezużytecznych. Najbardziej interesuje mnie środowisko nie oparte na GC (np .: iPhone).

Bezpośrednio z bloga Scotta:

„Słowo kluczowe assign wygeneruje metodę ustawiającą, która bezpośrednio przypisuje wartość do zmiennej instancji, zamiast ją kopiować lub zachowywać. Jest to najlepsze dla typów pierwotnych, takich jak NSInteger i CGFloat, lub obiektów, których bezpośrednio nie posiadasz, takich jak delegaci”.

Co to znaczy, że nie jesteś bezpośrednio właścicielem obiektu delegata? Zwykle zatrzymuję swoich delegatów, ponieważ jeśli nie chcę, aby odeszli w otchłań, zatrzymanie zajmie się tym za mnie. Zwykle abstrakcyjne UITableViewController z dala od odpowiedniego źródła danych i deleguj również. Zachowuję również ten konkretny przedmiot. Chcę mieć pewność, że nigdy nie zniknie, więc mój UITableView zawsze ma swojego delegata.

Czy ktoś może dalej wyjaśnić, gdzie / dlaczego się mylę, aby zrozumieć ten wspólny paradygmat w programowaniu Objective-C 2.0 polegający na używaniu właściwości assign na delegatach zamiast zachowania?

Dzięki!

Plumenator
źródło
Oznaczono ponownie tagiem „delegaci” i bez „iphone”.
Quinn Taylor
dlaczego delegat przypisuje się zamiast kopiować (jak NSString?)
OMGPOP

Odpowiedzi:

175

Powodem, dla którego unikasz zatrzymywania delegatów, jest to, że musisz unikać cyklu przechowywania:

A tworzy B A ustawia się jako delegat B… A zostaje zwolniony przez swojego właściciela

Gdyby B zatrzymał A, A nie zostałby zwolniony, ponieważ B jest właścicielem A, więc dealloc A nigdy nie zostałby sprawdzony, powodując zarówno A, jak i B. wyciek

Nie powinieneś się martwić, że A odejdzie, ponieważ jest właścicielem B i tym samym pozbywa się go w trybie dealloc.

Andrew Pouliot
źródło
Nie zgadzam się, Mike. Właśnie znalazłem problem polegający na tym, że modal ma delegata, który odrzuca modal. Ale kiedy robię ostrzeżenie pamięci w trybie modalnym, zwalnia delegata. Wtedy, kiedy idę odrzucić mój modal, delegat jest zerowy. Wypadek.
Paul Shapiro,
Ok, więc nie zgadzam się, ale masz rację, że to wada projektowa. Dowiedziałem się, że przekazuję swoje wezwanie do zwolnienia przez klasę dziecięcą prawdziwego zwolnienia. Ta klasa podrzędna została zwolniona i nie mogła przejść do delegata kontenera modalu w celu zwolnienia. Zmieniłem to, aby przekazać wskaźnik do ostatniego delegata, a ten nie został zwolniony z ostrzeżeniem dotyczącym pamięci i wszystko jest w porządku.
Paul Shapiro,
2
Twój kod nigdy nie powinien być napisany w taki sposób, aby delegat zerowy powodował awarię. Tylko obiekt będący właścicielem powinien mieć odniesienie do właściciela. Po cofnięciu alokacji musi ustawić delegatów posiadanych obiektów na zero przed ich zwolnieniem. Wtedy wszelkie wiadomości wysłane do delegata zerowego są po prostu ignorowane. Jednak przekazanie zerowego obiektu w wiadomości może spowodować awarię. Tylko upewnij się, że nie masz do czynienia z delegatami w ten sposób.
David Gish,
Czekaj - czy to nie jest to, co weakrobi? Pytanie brzmi, dlaczego używać assignzamiast weak?
wcochran
3
@wcochran: Nie, to pytanie brzmi: dlaczego używać assignzamiast retain. Pytanie jest starsze niż ARC; weaka strong(to ostatnie jest synonimem retain) nie istniało do czasu wprowadzenia ARC. Pytanie dotyczące weakvs. należy zadać assignosobno.
Peter Hosey
44

Ponieważ obiekt wysyłający komunikaty pełnomocnika nie jest właścicielem pełnomocnika.

Wiele razy dzieje się odwrotnie, tak jak gdy kontroler ustawia się jako delegat widoku lub okna: kontroler jest właścicielem widoku / okna, więc jeśli widok / okno jest właścicielem swojego delegata, oba obiekty byłyby właścicielami siebie nawzajem. Jest to oczywiście cykl zatrzymywania, podobny do wycieku z taką samą konsekwencją (obiekty, które powinny być martwe, pozostają żywe).

Innym razem obiekty są równorzędne: żaden z nich nie jest właścicielem drugiego, prawdopodobnie dlatego, że oba należą do tego samego trzeciego obiektu.

Tak czy inaczej, obiekt z delegatem nie powinien zachowywać swojego delegata.

(Nawiasem mówiąc, jest co najmniej jeden wyjątek. Nie pamiętam, co to było i nie sądzę, by istniał dobry powód).


Dodatek (dodano 19.05.2012): W ARC należy używać weakzamiast assign. Ustawiane są słabe referencjenil automatycznie, gdy obiekt umiera, co eliminuje możliwość, że delegujący obiekt zakończy wysyłanie wiadomości do martwego delegata.

Jeśli z jakiegoś powodu trzymasz się z dala od ARC, przynajmniej zmień assignwłaściwości, które wskazują na obiektyunsafe_unretained , które wyraźnie wskazują, że jest to niezerowe, ale niezerowe odniesienie do obiektu.

assign pozostaje odpowiedni dla wartości niebędących przedmiotem zarówno w ramach ARC, jak i MRC.

Peter Hosey
źródło
13
NSURLConnectionzachowuje swojego delegata.
Tak, użyj weak, ale to nie odpowiada na pierwotne pytanie: dlaczego Apple używa assignzamiast weak?
wcochran
@wcochran: Pierwotne pytanie brzmiało: „dlaczego delegowane właściwości są przekazywane, assigna nie zachowywane ”; weaknie istniało, kiedy o to pytano. Twoje pytanie jest inne, które powinieneś zadać osobno. Chętnie na nie odpowiem.
Peter Hosey
@wcochran i Peter, czy to pytanie zostało zadane gdzie indziej?
Mr Rogers,
17

Zwróć uwagę, że gdy masz delegata, który jest przypisany, bardzo ważne jest, aby zawsze ustawiać tę wartość delegata na zero, gdy obiekt ma zostać zwolniony - więc obiekt powinien zawsze uważać, aby zerować odwołania delegata w dealloc, jeśli tak nie jest zrobiono to gdzie indziej.

Kendall Helmstetter Gelner
źródło
„Zwróć uwagę, że kiedy masz przypisanego delegata, bardzo ważne jest, aby zawsze ustawiać wartość delegata na zero, gdy obiekt ma zostać zwolniony”. Dlaczego?
Peter Hosey
2
Ponieważ wszelkie pozostawione referencje będą nieprawidłowe po zwolnieniu obiektu (wskazując na pamięć, która nie jest już przydzielona do oczekiwanego rodzaju obiektu) - i tym samym spowoduje awarię, jeśli spróbujesz go użyć. Znakiem tego w debugerze jest sytuacja, w której debugger twierdzi, że jakaś zmienna ma typ, który wydaje się całkowicie nieprawidłowy w porównaniu z tym, jako faktycznie zadeklarowana zmienna.
Kendall Helmstetter Gelner
1
Jest to konieczne tylko wtedy, gdy obiekt, którego jesteś delegatem, jest zachowywany przez inne źródło, takie jak czasomierz lub inne asynchroniczne wywołanie zwrotne. W przeciwnym razie zostanie zwolniony po zwolnieniu i nie będzie próbował wywoływać metod delegata.
Andrew Pouliot,
@Andrew: To prawda, ale jeśli zawsze będziesz praktykował eliminowanie delegatów, nie zapomnisz, kiedy ma to znaczenie, lub jeśli przypadkowo zatrzymasz zatrzymany obiekt, który i tak pozostanie. Jeśli nie usuniesz delegata, wynikiem będzie tylko wyciek, a nie przeciek, po którym nastąpi awaria.
Kendall Helmstetter Gelner
1

Jednym z powodów jest unikanie zachowania cykli. Żeby uniknąć scenariusza, w którym obiekty A i B odwołują się do siebie i żaden z nich nie jest uwalniany z pamięci.

W rzeczywistości przypisywanie jest najlepsze dla typów pierwotnych, takich jak NSInteger i CGFloat, lub obiektów, których bezpośrednio nie posiadasz, takich jak delegaci.

Puneet Sharma
źródło
To raczej skopiowane odpowiednio z cytatu PO i zaakceptowanej odpowiedzi, prawda?
Dakab