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ć.