Krótka odpowiedź
Zamiast self
bezpośredniego dostępu , należy uzyskać do niego dostęp pośrednio, z referencji, która nie zostanie zachowana. Jeśli nie korzystasz z automatycznego zliczania referencji (ARC) , możesz to zrobić:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Słowo __block
kluczowe oznacza zmienne, które można modyfikować wewnątrz bloku (nie robimy tego), ale także nie są one automatycznie zachowywane, gdy blok jest zachowywany (chyba że używasz ARC). Jeśli to zrobisz, musisz mieć pewność, że nic więcej nie będzie próbowało wykonać bloku po zwolnieniu instancji MyDataProcessor. (Biorąc pod uwagę strukturę kodu, nie powinno to stanowić problemu.) Przeczytaj więcej na temat__block
.
Jeśli używasz ARC , semantyka __block
zmian i odniesienie zostaną zachowane, w takim przypadku powinieneś je zadeklarować __weak
.
Długa odpowiedź
Powiedzmy, że masz taki kod:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
Problem polega na tym, że self zachowuje odniesienie do bloku; tymczasem blok musi zachować odniesienie do siebie, aby pobrać właściwość delegata i wysłać delegatowi metodę. Jeśli wszystko inne w aplikacji zwolni odniesienie do tego obiektu, jego liczba zatrzymań nie będzie wynosić zero (ponieważ blok wskazuje na niego), a blok nie robi nic złego (ponieważ obiekt wskazuje na niego), a więc para obiektów wycieknie na stos, zajmując pamięć, ale na zawsze nieosiągalna bez debuggera. Naprawdę tragiczne.
Ten przypadek można łatwo naprawić, wykonując następujące czynności:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
W tym kodzie self zachowuje blok, blok zachowuje delegata i nie ma żadnych cykli (stąd widoczny; delegat może zachować nasz obiekt, ale to obecnie nie jest w naszych rękach). Ten kod nie ryzykuje wycieku w ten sam sposób, ponieważ wartość właściwości delegowania jest przechwytywana podczas tworzenia bloku, a nie sprawdzana podczas jego wykonywania. Efektem ubocznym jest to, że jeśli zmienisz delegata po utworzeniu tego bloku, blok nadal będzie wysyłał wiadomości o aktualizacji do starego delegata. To, czy tak się stanie, zależy od Twojej aplikacji.
Nawet jeśli jesteś fajny z tego zachowania, nadal nie możesz użyć tej sztuczki w twoim przypadku:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Tutaj przekazujesz self
bezpośrednio do delegata w wywołaniu metody, więc musisz go gdzieś tam zabrać. Jeśli masz kontrolę nad definicją typu bloku, najlepszą rzeczą byłoby przekazanie delegata do bloku jako parametru:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
To rozwiązanie pozwala uniknąć cyklu przechowywania i zawsze wywołuje bieżącego delegata.
Jeśli nie możesz zmienić bloku, możesz sobie z tym poradzić . Powodem, dla którego cykl przechowywania jest ostrzeżeniem, a nie błędem, jest to, że niekoniecznie oznacza to zgubę dla twojej aplikacji. Jeśli MyDataProcessor
jest w stanie zwolnić bloki po zakończeniu operacji, zanim jego rodzic spróbuje je zwolnić, cykl zostanie przerwany i wszystko zostanie poprawnie wyczyszczone. Jeśli możesz być tego pewien, to dobrym pomysłem byłoby użycie a, #pragma
aby ukryć ostrzeżenia dla tego bloku kodu. (Lub użyj flagi kompilatora na plik. Ale nie wyłączaj ostrzeżenia dla całego projektu.)
Możesz także rozważyć zastosowanie podobnej sztuczki powyżej, uznając referencję za słabą lub nie utrzymaną i używając tego w bloku. Na przykład:
__weak MyDataProcessor *dp = self; // OK for iOS 5 only
__unsafe_unretained MyDataProcessor *dp = self; // OK for iOS 4.x and up
__block MyDataProcessor *dp = self; // OK if you aren't using ARC
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Wszystkie trzy powyższe dadzą ci odniesienie bez zachowania wyniku, chociaż wszystkie zachowują się nieco inaczej: __weak
spróbują zerować odniesienie, gdy obiekt zostanie zwolniony; __unsafe_unretained
pozostawi niepoprawny wskaźnik; __block
doda inny poziom pośredni i pozwoli zmienić wartość referencji w obrębie bloku (w tym przypadku dp
nie ma znaczenia, ponieważ nie jest nigdzie indziej używany).
To, co najlepsze, będzie zależeć od tego, jaki kod możesz zmienić, a czego nie. Ale mam nadzieję, że dostarczyło ci to kilku pomysłów, jak postępować.
dp
zostanie zwolniony (na przykład, jeśli był to kontroler widoku i został wyskakujący), to wiersz[dp.delegate ...
spowoduje EXC_BADACCESS?strong
lubweak
?@weakify(..)
i@strongify(...)
który pozwala na użycieself
w bloku w sposób nie zachowujący.Istnieje również możliwość wyłączenia ostrzeżenia, gdy masz pewność, że cykl zostanie przerwany w przyszłości:
W ten sposób nie musisz ćwiczyć
__weak
,self
aliasingu i wyraźnego prefiksu ivar.źródło
__weak id weakSelf = self;
ma zasadniczo inne zachowanie niż tłumienie ostrzeżenia. Pytanie zaczęło się od „... jeśli masz pewność, że cykl przechowywania zostanie przerwany”[array addObject:weakObject];
jeśli słaby obiekt został zwolniony, powoduje to awarię. Oczywiście nie jest to preferowane w stosunku do cyklu przechowywania. Musisz zrozumieć, czy twój blok faktycznie żyje wystarczająco długo, aby uzasadnić osłabienie, a także czy chcesz, aby akcja w bloku zależała od tego, czy słaby obiekt jest nadal ważny.Dla wspólnego rozwiązania mam je zdefiniować w nagłówku prekompilacji. Unika przechwytywania i nadal włącza pomoc kompilatora, unikając jego użycia
id
Następnie w kodzie możesz wykonać:
źródło
self
twojego bloku @weakify (self); blok id = ^ {@strongify (self); [self.delegate myAPIDidFinish: self]; };Uważam, że rozwiązanie bez ARC działa również z ARC, używając
__block
słowa kluczowego:EDYCJA: Zgodnie z Informacjami o przejściu na ARC obiekt zadeklarowany z
__block
pamięcią jest nadal zachowywany. Użyj__weak
(preferowane) lub__unsafe_unretained
(dla kompatybilności wstecznej).źródło
__block
słowo kluczowe unikało zachowania swojego odniesienia. Dzięki! Zaktualizowałem moją monolityczną odpowiedź. :-)Łącząc kilka innych odpowiedzi, oto, czego teraz używam dla wpisanego słabego „ja” do użycia w blokach:
Ustawiłem to jako fragment kodu XCode z prefiksem zakończenia „welf” w metodach / funkcjach, który trafia po wpisaniu tylko „my”.
źródło
ostrzeżenie => „przechwycenie siebie w bloku może prowadzić do zatrzymania cyklu”
gdy odwołujesz się do siebie lub jego własności w bloku, który jest silnie zachowywany przez siebie, niż pokazuje to powyżej ostrzeżenia.
więc aby tego uniknąć, musimy zrobić z tego tydzień ref
więc zamiast używać
powinniśmy użyć
uwaga: cykl zachowania występuje zwykle wtedy, gdy dwa obiekty odnoszące się do siebie, przez które oba mają liczbę referencji = 1 i ich metoda delloc nigdy nie zostanie wywołana.
źródło
Nowym sposobem na to jest użycie @weakify i @strongify marco
Więcej informacji o @Weakify @Strongify Marco
źródło
Jeśli masz pewność, że kod nie utworzy cyklu przechowywania lub że cykl zostanie przerwany później, najprostszym sposobem wyciszenia ostrzeżenia jest:
Powodem tego jest fakt, że podczas gdy dostęp do właściwości za pomocą kropek jest uwzględniany w analizie Xcode, a zatem
jest postrzegany jako mający zachowanie x przez y (po lewej stronie przypisania) i y przez x (po prawej stronie), wywołania metod nie podlegają tej samej analizie, nawet jeśli są wywołaniami metod dostępu do właściwości które są równoważne z dostępem punktowym, nawet jeśli te metody dostępu do właściwości są generowane przez kompilator, więc w
tylko prawa strona jest postrzegana jako tworzenie zatrzymania (przez y x) i nie jest generowane ostrzeżenie o cyklu zatrzymania.
źródło