ARC __block и __weak

Допустим, я пытаюсь получить доступ self из блока:

[someObject successBlock:^(NSArray *result) {
    [self someSuccessMethod];
} failure:^(NSString *errorMessage, int status) {
    [self someFailureMethod];
}];

Я понимаю, что это создает цикл сохранения и что someObject а также self никогда не делайся выделенным

Что меня смущает, так это то, что на самом деле происходит с / без __block ключевое слово. Я могу исправить цикл сохранения, сделав __weak ссылка на себя:

__weak MyClass* me = self;
[someObject successBlock:^(NSArray *result) {
    [me someSuccessMethod];
} failure:^(NSString *errorMessage, int status) {
    [me someFailureMethod];
}];

Мне не нужно использовать __block здесь, потому что я не пытаюсь изменить me изнутри блока. Из того, что я понимаю, если я не использую __block, копия me ссылка внутри блока. Мой вопрос: если то, на что ссылается внутри блока, является просто копией объекта, почему оригинальный блок кода создает цикл сохранения? Я предполагаю, что ссылка на self это просто копия, так как я никогда не использую __block ключевое слово. Я думаю об этом неправильно?

4 ответа

Решение

В первом случае блок захватывает selfт.е. он сохраняет копию self как еще один сильный указатель. Это увеличивает количество сохраняемых объектов и указывает на цикл сохранения.

Во втором случае блок захватывает meт.е. он сохраняет копию me как еще один слабый указатель. Это не увеличивает счет сохранения и, следовательно, не вызывает циклов сохранения.

(Если вы печатаете адрес me снаружи и внутри блока вы увидите, что адреса разные. Блок имеет свой слабый указатель на объект.)

Если указанный объект освобожден, все слабые ссылки (включая сохраненную блоком) устанавливаются в nil по времени выполнения Objective-C.

(Я просто надеюсь, что понял это правильно.)

Цикл сохранения происходит, когда два объекта хранят сильную ссылку друг на друга. Самый простой случай - это объект a хранение сильной ссылки на объект b а также b делать противоположное [1]. Циклы сохранения являются проблемой в Objective-C, потому что они заставляют ARC полагать, что эти объекты всегда используются, даже если на эти объекты не ссылаются откуда-либо еще.

Давайте рассмотрим несколько примеров. У вас есть объект z который выделяет a а также b, использует их, а затем избавляется от них. Если a а также b создал удерживающий цикл между собой в первую очередь, a а также b не будет освобожден Если вы сделаете это несколько раз, у вас будет серьезная утечка памяти.

Другой реальный пример сохранения цикла - это если a выделяет и настоятельно ссылается на b объект, но вы также храните сильную ссылку от b в a (многим меньшим объектам в графе объектов может потребоваться доступ к их родителям).

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

Другое решение (как правило, менее изящное, но, возможно, подходящее в некоторых ситуациях) может заключаться в использовании какого-либо cleanup метод в a это ноль его ссылка на b, таким образом b будет освобожден, когда cleanup называется (если b не сильно упоминается в другом месте). Это громоздко, потому что вы не можете сделать это из a "s dealloc (он никогда не вызывается, если есть цикл сохранения) и потому что вы должны помнить, чтобы позвонить cleanup в подходящее время.

  1. Обратите внимание, что циклы сохранения также транзитивны (например, объект a настоятельно ссылки b который настоятельно ссылается c который настоятельно ссылается a).

При всем этом сказано: управление памятью блоков довольно сложно понять.

Ваш первый пример может создать цикл временного хранения (и только если ваш self объект хранит сильную ссылку на someObject). Этот цикл временного хранения исчезает, когда блок заканчивает выполнение и освобождается.

Во время исполнения self будет хранить ссылку на someObject, someObject к block и block в self снова. Но опять же, это только временно, потому что блок нигде не хранится постоянно (если только [someObject successBlock:failure:] реализация делает это, но это не часто для блоков завершения).

Таким образом, цикл сохранения не является проблемой в вашем первом примере.

Как правило, сохранение циклов внутри блоков является проблемой, только если какой-то объект хранит блок, а не выполняет его напрямую. Тогда легко увидеть, что self настоятельно ссылается на block и block имеет сильную ссылку на self, Обратите внимание, что доступ к любому ивару изнутри блока автоматически создает сильную ссылку на self в этом блоке.

Используется эквивалент того, чтобы убедиться, что содержащийся объект не имеет строгой ссылки на его контейнер. __weak SelfClass *weakSelf = self для доступа как к методам, так и к ivars (лучше, если вы обращаетесь к ivar через аксессоры, как при использовании свойств). Ссылка вашего блока на self будет слабым (это не копия, это слабая ссылка), и это позволит self на de освобождается, когда на него больше нет сильных ссылок.

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


__block редко используется в переменных, которые указывают на объекты, потому что Objective-C не обеспечивает неизменность таких объектов.

Если у вас есть указатель на объект, вы можете вызывать его методы, и эти методы могут изменять его, с или без __block, __block больше (только?) полезно для переменных базовых типов (int, float и т. д.). Смотрите здесь, что происходит, когда вы используете __block с переменной указателя объекта. Вы также можете прочитать больше о __block в блоках программирования тем Apple.

Редактировать: Исправлена ​​ошибка, связанная с __block использование на указателях объекта. Спасибо @KevinDiTraglia за указание на это.

Ваш первый пример не создаст бесконечный цикл сохранения. Хорошо, будет цикл сохранения, но как только блоки будут сделаны, ссылка из блоков на someObject будут удалены Итак someObject будет жить по крайней мере, пока блоки не будут сделаны. Такой цикл временного хранения может быть хорошим или плохим, в зависимости от того, что вы хотите:

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

Например. myObject - это контроллер представления, который в этих блоках получает изображение из сети. Если вы поп someObject Из контроллера навигации контроллер не сможет отображать изображение после его извлечения, поэтому нет необходимости сохранять его. Успех или ошибка не имеют значения, пользователь больше не интересуется картиной someObject должен был принести. В таком случае использование слабого - лучший вариант, однако код в блоках должен ожидать self может быть ноль.

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

И вы недовольны "someObject и self никогда не освобождаются": self будет освобождено, когда блоки будут освобождены. Блоки будут освобождены с помощью someObject. SomeObject будет освобожден, когда у него больше нет ссылок. Так что если ваш самообъект владеет someObject, просто отпустите someObject, когда он вам больше не нужен.

Другие вопросы по тегам