В C правильность не может быть выведена из запуска любого количества тестов, поскольку вы можете видеть неопределенное поведение. Чтобы правильно знать, что правильно, вам нужно обратиться к языковой спецификации. В этом случае ARC specification.
Поучительно сначала просмотреть, когда необходимо скопировать блок под MRC. В принципе, блок, который захватывает переменные, может начинаться в стеке. Это означает, что когда вы видите блок-литерал, компилятор может заменить его скрытой локальной переменной в этой области, которая содержит структуру объекта по значению. Поскольку локальные переменные действительны только в области, в которой они объявлены, поэтому блоки из блок-литералов действительны только в области, в которой находится литерал, если только он не скопирован.
Кроме того, существует дополнительное правило, которое, если функция принимает параметр типа указателя блока, не делает никаких предположений о том, является ли это стекным блоком или нет. Гарантируется, что блок действителен во время вызова блока.Однако это в значительной степени означает, что блок действителен для всей продолжительности вызова функции, потому что 1) если это стекный блок, и он действителен при вызове функции, это означает, что где-то вверх по стеку, где был блок созданный, вызов все еще находится в пределах литерала стека; поэтому он по-прежнему будет находиться в области действия к концу вызова функции; 2) если это кучный блок или глобальный блок, он подчиняется тем же правилам управления памятью, что и другие объекты.
Из этого можно сделать вывод, где необходимо скопировать. Давайте рассмотрим некоторые случаи:
- Если блок из блока буквального возвращаются из функции: Он должен быть скопирован, поскольку блок выходит из рамки буквального
- Если блок из блока буквального хранится в переменной экземпляра: его нужно скопировать, поскольку блок выходит из области литерала
- Если блок захвачен другим блоком: его не нужно копировать, поскольку блок захвата, если он скопирован , сохранит все захваченные переменные типа объекта и скопирует все захваченные переменные типа блока. Таким образом, единственная ситуация, когда наш блок будет сбежать из этой области, будет, если блок, который захватывает его, выходит из области; но для этого нужно скопировать этот блок, который, в свою очередь, копирует наш блок.
- Если блок из литерала блока передается другой функции, а параметр этой функции имеет тип указателя блока: его не нужно копировать, так как функция не предполагает, что она была скопирована. Это означает, что любая функция, которая берет блок и должна «хранить его позже», должна взять на себя ответственность за копирование блока. И действительно, это так (например,
dispatch_async
).
- Если блок из литерала блока передан другой функции, а параметр этой функции - , а не типа указателя блока (например,
-addObject:
): его нужно скопировать, если вы знаете, что эта функция сохраняет его позже. Причина, по которой его нужно скопировать, заключается в том, что функция не может взять на себя ответственность за копирование блока, так как не знает, что он берет блок.
Так что если ваш код в вопросе находился в MRC, -whatever
не нужно было ничего копировать. -whenSettledDo:
необходимо будет скопировать блок, поскольку он передается в addObject:
, метод, который принимает общий объект, введите id
и не знает, что он берет блок.
Теперь давайте посмотрим, какая из этих копий ARC позаботится о вас. Section 7.5 говорит
За исключением сохраняет сделано в рамках инициализации __strong переменного параметр или чтения __weak переменного, всякий раз, когда эти семантики вызова для сохранения значения типа блока указателей, он имеет эффект Блок-копия. Оптимизатор может удалить такие копии, когда видит, что результат используется только в качестве аргумента для вызова.
Что означает первая часть, так это то, что в большинстве мест, где вы назначаете сильную ссылку на тип указателя блока (который обычно вызывает сохранение для типов указателей объектов), он будет скопирован. Однако есть некоторые исключения: 1) В начале первого предложения он говорит, что параметр типа указателя блока не может быть скопирован; 2) Во втором предложении говорится, что если блок используется только как аргумент для вызова, он не может быть скопирован.
Что это означает для кода в вашем вопросе? handlerFixed
- это сильная ссылка на тип указателя блока, и результат используется в двух местах, больше, чем просто аргумент для вызова, поэтому присвоение ему присваивает копию. Если, однако, вы передали блок-литералы непосредственно на addObject:
, тогда не гарантируется копия (поскольку она используется только в качестве аргумента для вызова), и вам нужно будет ее явно скопировать (поскольку мы обсудили, что блок передано в addObject:
необходимо скопировать).
Когда вы использовали settledHandler
непосредственно, так как settledHandler
является параметром, не копируются автоматически, поэтому, когда вы передаете его addObject:
, вам необходимо скопировать его в явном виде, так как мы уже говорили, что блок передан addObject:
потребности быть скопировано.
Итак, в заключении, в АРК вам нужно явно копировать при переходе блока в функцию, которая конкретно не принимают аргументы блока (как addObject:
), если это блок буквальное, или это изменяемый параметр, который вам Проходит.
Спасибо, это действительно хорошее освещение того, что связано. Не могли бы вы прокомментировать, гарантированно ли присвоение локальному сайту необходимой копии, или если это детали реализации, которые могут измениться? Язык об удалении копий оптимизатора заставляет меня думать, что мне нужно явно скопировать перед назначением на локальный. –
@Strilanc: Я думаю, что назначение локального типа указателя блока будет копировать его. Я не уверен, относится ли часть о «результат используется только как аргумент к вызову», когда вы передаете литерал блока напрямую или если он также включает локальный, который используется только в одном месте, в качестве аргумента , – newacct