Obawiam się, że to pytanie jest dość proste, ale myślę, że jest istotne dla wielu programistów Objective-C, którzy wchodzą do bloków.
Słyszałem, że skoro bloki przechwytują zmienne lokalne, do których odwołują się w nich jako const
kopie, użycie self
w bloku może skutkować cyklem zachowania, jeśli ten blok zostanie skopiowany. Więc powinniśmy użyć __block
do wymuszenia bezpośredniego działania bloku self
zamiast kopiowania.
__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];
zamiast po prostu
[someObject messageWithBlock:^{ [self doSomething]; }];
Chciałbym wiedzieć, co następuje: jeśli to prawda, czy istnieje sposób, żebym mógł uniknąć brzydoty (poza używaniem GC)?
objective-c
memory-management
objective-c-blocks
Jonathan Sterling
źródło
źródło
self
proxythis
tylko po to, żeby coś odwrócić. W JavaScript nazywam mojethis
domknięciaself
, więc jest to przyjemne i zrównoważone. :)Odpowiedzi:
Ściśle mówiąc, fakt, że jest to kopia const, nie ma nic wspólnego z tym problemem. Bloki zachowają wszelkie wartości obj-c przechwycone podczas ich tworzenia. Tak się składa, że obejście problemu const-copy jest identyczne jak obejście problemu zachowania; mianowicie przy użyciu
__block
klasy pamięci dla zmiennej.W każdym razie, odpowiadając na twoje pytanie, nie ma tutaj prawdziwej alternatywy. Jeśli projektujesz własny interfejs API oparty na blokach i ma to sens, możesz poprosić o przekazanie bloku wartości
self
in jako argumentu. Niestety nie ma to sensu w przypadku większości interfejsów API.Należy pamiętać, że odwoływanie się do ivar ma dokładnie ten sam problem. Jeśli potrzebujesz odwołać się do ivar w swoim bloku, użyj zamiast tego właściwości lub użyj
bself->ivar
.Dodatek: Podczas kompilacji jako ARC,
__block
nie przerywa już zachowań cykli. Jeśli kompilujesz dla ARC, musisz użyć__weak
lub__unsafe_unretained
zamiast tego.źródło
__weak
jest w porządku. Jeśli wiesz na pewno, że obiekt nie może znajdować się poza zasięgiem, gdy blok jest wywoływany,__unsafe_unretained
jest to nawet nieco szybsze, ale ogólnie nie ma to znaczenia. Jeśli używasz__weak
, upewnij się, że wrzucisz ją do__strong
zmiennej lokalnej i przetestuj ją na non-,nil
zanim cokolwiek z nią zrobisz.__block
Efekt uboczny nie zatrzymywania i puszczania był wyłącznie z powodu niemożności prawidłowego uzasadnienia tego. Dzięki ARC kompilator zyskał tę zdolność, więc__block
teraz zachowuje i wydaje. Jeśli chcesz tego uniknąć, musisz użyć polecenia__unsafe_unretained
, które instruuje kompilator, aby nie wykonywał żadnych zachowań ani zwolnień wartości w zmiennej.Po prostu użyj:
Więcej informacji: WWDC 2011 - Bloki i Grand Central Dispatch w praktyce .
https://developer.apple.com/videos/wwdc/2011/?id=308
Uwaga: jeśli to nie zadziała, możesz spróbować
źródło
Może to być oczywiste, ale musisz zrobić brzydki
self
alias tylko wtedy, gdy wiesz, że otrzymasz cykl przechowywania. Jeśli blok jest jednorazowy, myślę, że możesz bezpiecznie zignorować zatrzymanieself
. Na przykład zły przypadek ma miejsce, gdy masz blok jako interfejs wywołania zwrotnego. Jak tutaj:Tutaj API nie ma większego sensu, ale miałoby sens na przykład podczas komunikacji z superklasą. Zachowujemy obsługę bufora, obsługę bufora zachowuje nas. Porównaj z czymś takim:
W takich sytuacjach nie wykonuję
self
aliasingu. Otrzymujesz cykl zachowania, ale operacja jest krótkotrwała i blok w końcu wyjdzie z pamięci, przerywając cykl. Ale moje doświadczenie z blokami jest bardzo małe i może się zdarzyć, żeself
aliasing okaże się najlepszą praktyką na dłuższą metę.źródło
copy
, a nieretain
. Jeśli są po prosturetain
, nie ma gwarancji, że zostaną usunięte ze stosu, co oznacza, że kiedy zaczniesz go wykonywać, już go tam nie będzie. (a kopiowanie i już skopiowany blok jest zoptymalizowany do zachowania)retain
fazę jakiś czas temu i szybko zorientowałem się, o czym mówisz :) Dzięki!retain
jest to całkowicie ignorowane w przypadku bloków (chyba że zostały już przesunięte ze stosucopy
).Publikowanie kolejnej odpowiedzi, ponieważ to też był problem dla mnie. Początkowo myślałem, że muszę użyć blockSelf wszędzie tam, gdzie wewnątrz bloku było odniesienie do siebie. Tak nie jest, dzieje się tak tylko wtedy, gdy sam obiekt ma w sobie blok. I faktycznie, jeśli użyjesz blockSelf w takich przypadkach, obiekt może zostać zwolniony, zanim otrzymasz wynik z powrotem z bloku, a następnie ulegnie awarii, gdy spróbuje go wywołać, więc wyraźnie chcesz, aby został zachowany do czasu odpowiedzi wraca.
Pierwszy przypadek pokazuje, kiedy wystąpi cykl utrzymania, ponieważ zawiera blok, do którego odwołuje się blok:
W drugim przypadku nie potrzebujesz blockSelf, ponieważ obiekt wywołujący nie ma w sobie bloku, który spowoduje cykl przechowywania, gdy odwołasz się do siebie:
źródło
self
mogą nie być spowodowane nadmiernym stosowaniem tej poprawki przez ludzi. To dobry przykład unikania cykli przechowywania w kodzie innym niż ARC, dziękujemy za wysłanie.Pamiętaj również, że cykle zachowania mogą wystąpić, jeśli twój blok odnosi się do innego obiektu, który następnie zachowuje
self
.Nie jestem pewien, czy Garbage Collection może pomóc w tych cyklach przechowywania. Jeśli obiekt zachowujący blok (który nazwiemy obiektem serwera) przeżyje
self
(obiekt klienta), odniesienie doself
wnętrza bloku nie zostanie uznane za cykliczne, dopóki sam obiekt zatrzymujący nie zostanie zwolniony. Jeśli obiekt serwera znacznie przeżyje swoich klientów, możesz mieć znaczny wyciek pamięci.Ponieważ nie ma czystych rozwiązań, polecam następujące obejścia. Możesz wybrać jedną lub więcej z nich, aby rozwiązać problem.
doSomethingAndWhenDoneExecuteThisBlock:
, a nie metod takich jaksetNotificationHandlerBlock:
. Bloki użyte do ukończenia mają określony koniec życia i powinny zostać zwolnione przez obiekty serwera po ich ocenie. Zapobiega to zbyt długiemu cyklowi utrzymania, nawet jeśli wystąpi.dealloc
zamiastrelease
.Jeśli piszesz obiekt serwera, argumenty blokowe przyjmuj tylko do uzupełnienia. Nie akceptuj argumentów blokowych dla wywołań zwrotnych, takich jak
setEventHandlerBlock:
. Zamiast tego powróć do klasycznego wzorca delegata: utwórz formalny protokół i rozgłaszajsetEventDelegate:
metodę. Nie zatrzymuj pełnomocnika. Jeśli nawet nie chcesz tworzyć formalnego protokołu, zaakceptuj selektor jako wywołanie zwrotne delegata.I wreszcie, ten wzorzec powinien wywoływać alarmy:
Jeśli próbujesz odhaczyć bloki, które mogą odnosić się do
self
środkadealloc
, już masz kłopoty.dealloc
może nigdy nie zostać wywołane z powodu cyklu przechowywania spowodowanego przez odwołania w bloku, co oznacza, że obiekt po prostu wycieknie, dopóki obiekt serwera nie zostanie zwolniony.źródło
__weak
odpowiednio.__block __unsafe_unretained
modyfikatory sugerowane w poście Kevina mogą powodować wyjątek złego dostępu w przypadku bloku wykonywanego w innym wątku. Lepiej jest używać tylko modyfikatora __block dla zmiennej temp i po użyciu ustawić ją na zero.źródło
Możesz użyć biblioteki libextobjc. Jest dość popularny, używany np. W ReactiveCocoa. https://github.com/jspahrsummers/libextobjc
Zapewnia 2 makra @weakify i @strongify, dzięki czemu możesz mieć:
Zapobiega to bezpośredniemu silnemu odniesieniu, więc nie wchodzimy w cykl zachowania siebie. Zapobiega również zmniejszeniu się wartości self do zera w połowie, ale nadal odpowiednio zmniejsza liczbę zatrzymań. Więcej w tym linku: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html
źródło
Co powiesz na to?
Nie dostaję już ostrzeżenia kompilatora.
źródło
Blok: wystąpi cykl utrzymania, ponieważ zawiera on blok, do którego istnieje odniesienie w bloku; Jeśli utworzysz kopię bloku i użyjesz zmiennej składowej, self zachowa.
źródło