Как избежать захвата себя в блоках при реализации API? - PullRequest
222 голосов
/ 21 октября 2011

У меня есть работающее приложение, и я работаю над его преобразованием в 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?

Ответы [ 8 ]

509 голосов
/ 21 октября 2011

Краткий ответ

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

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

25 голосов
/ 14 февраля 2012

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

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

14 голосов
/ 21 сентября 2013

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

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

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

BlockWeakSelf weakSelf = self;
self.dataProcessor.completion = ^{
    [weakSelf.delegate myAPIDidFinish:weakSelf];
    weakSelf.dataProcessor = nil;
};
11 голосов
/ 13 мая 2014

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

__typeof(self) __weak welf = self;

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

11 голосов
/ 21 октября 2011

Я считаю, что решение без ARC также работает с ARC, используя ключевое слово __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];
6 голосов
/ 30 июня 2014

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

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

поэтому, чтобы избежать этого, мы должны указать недельную ссылку

__weak typeof(self) weakSelf = self;

, поэтому вместо

blockname=^{
    self.PROPERTY =something;
}

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

blockname=^{
    weakSelf.PROPERTY =something;
}

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

1 голос
/ 26 января 2018

Новый способ сделать это - использовать @weakify и @strongify marco

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

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

0 голосов
/ 07 января 2014

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

// 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 of y (слева от присвоения) и y y x (справа), вызовы методов не подвергаются такому же анализу, даже если они являются свойством вызовы методов доступа, эквивалентные точечному доступу, даже если эти методы доступа к свойствам генерируются компилятором, поэтому в

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

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

...