Как избежать захвата себя в блоках при реализации API?



у меня есть рабочее приложение, и я работаю над преобразованием его в ARC в Xcode 4.2. Одно из предупреждений предварительной проверки включает захват self сильно в блоке, ведущем к циклу сохранения. Я сделал простой пример кода, чтобы проиллюстрировать проблему. Я считаю, что понимаю, что это значит, но я не уверен, что "правильный" или рекомендуемый способ реализации этого типа сценария.




  • self является экземпляром класса MyAPI

  • приведенный ниже код упрощен, чтобы показать только взаимодействие с объектами и блоками, относящимися к моему вопросу

  • предположим, что MyAPI получает данные из удаленного источника и MyDataProcessor работает на этих данных и производит вывод

  • процессор настроен с блоками для передачи прогресса и состояния


код:



// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

self.dataProcessor.progress = ^(CGFloat percentComplete) {
[self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
[self.delegate myAPIDidFinish:self];
self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];


вопрос: что я делаю "неправильно" и/или как это должно быть изменено в соответствии с соглашениями ARC?

655   8  

8 ответов:

короткий ответ:

вместо self непосредственно, вы должны получить доступ к нему косвенно, из ссылки, которая не будет сохранена. если вы не используете автоматический подсчет ссылок (ARC), вы можете сделать это:

__block MyDataProcessor *dp = self;
self.progressBlock = ^(CGFloat percentComplete) {
    [dp.delegate myAPI:dp isProcessingWithProgress:percentComplete];
}

The __block ключевые слова помечают переменные, которые могут быть изменены внутри блока (мы этого не делаем), но также они не сохраняются автоматически при сохранении блока (если вы не используете ARC). Если вы сделаете это, вы необходимо убедиться, что после освобождения экземпляра MyDataProcessor больше ничего не будет пытаться выполнить блок. (Учитывая структуру вашего кода, это не должно быть проблемой.)подробнее о __block.

если вы используете ARC семантика __block изменения и ссылка будут сохранены, и в этом случае вы должны объявить его __weak вместо.

ответ

допустим, у вас был такой код это:

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

проблема здесь заключается в том, что self сохраняет ссылку на блок; между тем блок должен сохранить ссылку на 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];
};

это решение позволяет избежать цикла сохранения и всегда вызывает текущий делегат.

если вы не можете изменить блок, вы могли бы разберись с этим. Причина, по которой цикл сохранения является предупреждением, а не ошибкой, заключается в том, что они не обязательно пишут doom для вашего приложения. Если MyDataProcessor может освободить блоки, когда операция будет завершена, прежде чем его родитель попытается освободить его, цикл будет нарушен, и все будет очищено должным образом. Если бы вы могли быть уверены в этом, то правильным было бы использовать #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 нигде не используется).

что это лучшие будет зависеть от того, какой код вы можете изменить, а что нельзя. Но, надеюсь, это дало вам некоторые идеи о том, как поступить.

есть также опция, чтобы отключить предупреждение, когда вы уверены, что цикл будет нарушен в будущем:

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"

self.progressBlock = ^(CGFloat percentComplete) {
    [self.delegate processingWithProgress:percentComplete];
}

#pragma clang diagnostic pop

таким образом, вы не должны баловаться с __weak,self сглаживание и явный префикс ivar.

для общего решения у меня есть эти определения в заголовке precompile. Избегает захвата и по-прежнему позволяет компилятору помочь, избегая использовать id

#define BlockWeakObject(o) __typeof(o) __weak
#define BlockWeakSelf BlockWeakObject(self)

тогда в коде вы можете сделать:

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
    [weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;
};

Я считаю, что решение без дуги также работает с дугой, используя __block ключевые слова:

редактировать: за переход к заметкам о выпуске ARC, объект, объявленный с __block хранение все еще сохраняется. Используйте __weak (предпочтительно) или __unsafe_unretained (для обратной совместимости).

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

// Use this inside blocks
__block id myself = self;

self.dataProcessor.progress = ^(CGFloat percentComplete) {
    [myself.delegate myAPI:myself isProcessingWithProgress:percentComplete];
};

self.dataProcessor.completion = ^{
    [myself.delegate myAPIDidFinish:myself];
    myself.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

объединяя несколько других ответов, это то, что я использую сейчас для типизированного слабого я для использования в блоках:

__typeof(self) __weak welf = self;

Я установил это как Фрагмент Кода XCode с префиксом завершения " welf "в методах/функциях, который попадает после ввода только"мы".

warning = > "захват себя внутри блока, вероятно, приведет к циклу сохранения"

когда вы ссылаетесь на себя или его свойство внутри блока, который сильно сохраняется самим собой, чем это показано выше предупреждение.

Так что для избежания этого мы должны сделать это неделю ref

__weak typeof(self) weakSelf = self;

поэтому вместо использования

blockname=^{
    self.PROPERTY =something;
}

мы должны использовать

blockname=^{
    weakSelf.PROPERTY =something;
}

Примечание: сохранить цикл обычно происходит, когда некоторые, как два объекта, ссылающиеся друг на друга который имеет счетчик ссылок =1, и их метод delloc никогда не вызывается.

новый способ сделать это с помощью @weakify и @strongify marco

@weakify(self);
[self methodThatTakesABlock:^ {
    @strongify(self);
    [self doSomething];
}];

подробнее о @Weakify @Strongify Marco

Если вы уверены, что ваш код не создаст цикл сохранения, или что цикл будет нарушен позже, то самый простой способ заставить замолчать предупреждение:

// code sample
self.delegate = aDelegate;

self.dataProcessor = [[MyDataProcessor alloc] init];

[self dataProcessor].progress = ^(CGFloat percentComplete) {
    [self.delegate myAPI:self isProcessingWithProgress:percentComplete];
};

[self dataProcessor].completion = ^{
    [self.delegate myAPIDidFinish:self];
    self.dataProcessor = nil;
};

// start the processor - processing happens asynchronously and the processor is released in the completion block
[self.dataProcessor startProcessing];

причина, по которой это работает, заключается в том, что в то время как точечный доступ к свойствам учитывается анализом Xcode, и поэтому

x.y.z = ^{ block that retains x}

рассматривается как имеющий сохранение по x из y (на левой стороне назначения) и по y из x (на правой стороне), вызовы методов не подлежат одинаковому анализу, даже если они являются вызовами метода доступа к свойствам, эквивалентными точечному доступу, даже если эти методы доступа к свойствам генерируются компилятором, поэтому в

[x y].z = ^{ block that retains x}

только правая сторона рассматривается как создание сохранения (по y из x), и не создается предупреждение цикла сохранения.

Comments

    Ничего не найдено.