Коротка відповідь
Замість доступу self
безпосередньо, ви повинні отримати доступ до нього опосередковано, з посилання, яке не буде збережене. Якщо ви не використовуєте автоматичний підрахунок посилань (ARC) , ви можете зробити це:
__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}
Ці __block
ключові слова , знаки змінних , які можуть бути змінені всередині блоку (ми не робимо) , але і вони не зберігаються автоматично , коли блок зберігається (якщо ви не використовуєте ARC). Якщо ви це зробите, ви повинні бути впевнені, що більше нічого не буде намагатися виконати блок після звільнення екземпляра MyDataProcessor. (Враховуючи структуру вашого коду, це не повинно бути проблемою.) Детальніше про__block
.
Якщо ви використовуєте ARC , семантика __block
змін і посилання будуть збережені, і в цьому випадку ви повинні __weak
замість цього оголосити .
Довга відповідь
Скажімо, у вас був такий код:
self.progressBlock = ^(CGFloat percentComplete) {
[self.delegate processingWithProgress:percentComplete];
}
Проблема тут полягає в тому, що self зберігає посилання на блок; тим часом блок повинен зберігати посилання на себе, щоб отримати своє властивість делегата та надіслати делегат методу. Якщо все інше у вашій програмі звільнить посилання на цей об’єкт, кількість його збереження не буде нульовою (тому що блок вказує на нього) і блок нічого не робить (тому що об’єкт вказує на нього) і так пара предметів просочиться до купи, займаючи пам'ять, але назавжди недоступна без налагоджувача. Трагічно, справді.
Цей випадок можна легко виправити, зробивши це замість цього:
id progressDelegate = self.delegate;
self.progressBlock = ^(CGFloat percentComplete) {
[progressDelegate processingWithProgress:percentComplete];
}
У цьому коді self зберігає блок, блок утримує делегата, і циклів немає (видно звідси; делегат може зберегти наш об’єкт, але це зараз поза нашими руками). Цей код не ризикує протікати таким же чином, оскільки значення властивості делегата захоплюється під час створення блоку, а не дивиться під час його виконання. Побічним ефектом є те, що якщо ви зміните делегата після створення цього блоку, блок все одно буде надсилати повідомлення оновлення старому делегату. Чи це може статися чи ні, залежить від вашої заявки.
Навіть якщо ви були здорові з такою поведінкою, ви все одно не можете використовувати цей трюк у своєму випадку:
self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};
Тут ви переходите self
безпосередньо до делегата у виклику методу, тому вам доведеться десь там потрапити. Якщо ви маєте контроль над визначенням типу блоку, найкраще було б передати делегат в блок як параметр:
self.dataProcessor.progress = ^(MyDataProcessor *dp, CGFloat percentComplete) {
[dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
};
Це рішення дозволяє уникнути циклу збереження і завжди викликає поточного делегата.
Якщо ви не можете змінити блок, ви можете з ним впоратися . Причина, що цикл збереження є попередженням, а не помилкою, полягає в тому, що вони не обов'язково вимовляють прирік для вашої програми. Якщо MyDataProcessor
зможе звільнити блоки, коли операція завершена, перш ніж її батько спробує звільнити її, цикл буде порушений і все буде очищено належним чином. Якщо ви можете бути впевнені в цьому, то правильним, що потрібно зробити, буде використання a #pragma
для придушення попереджень для цього блоку коду. (Або використовуйте прапор компілятора на файл. Але не вимикайте попередження для всього проекту.)
Ви також можете розглянути подібний трюк вище, оголосивши посилання слабким або незатриманим і використовуючи його в блоці. Наприклад:
__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];
}
Усі три вищевикладені дадуть вам посилання, не зберігаючи результат, хоча всі вони поводяться трохи інакше: __weak
спробують нуль посилання, коли об’єкт буде звільнений; __unsafe_unretained
залишить вас недійсним покажчиком; __block
фактично додасть ще один рівень непрямості і дозволить вам змінити значення посилання зсередини блоку (не має значення в цьому випадку, оскільки dp
не використовується більше ніде).
Що найкраще буде залежати від того, який код ви можете змінити, а який ви не можете. Але, сподіваємось, це дало вам кілька ідей, як діяти далі.