1

Я получаю противоречивую информацию о том, когда мне нужно скопировать блок при использовании ARC в объективе-C. Совет варьируется от «всегда» до «никогда», поэтому я действительно не уверен, что с этим делать.Когда * точно * необходимо скопировать блок в объектив-C под ARC?

Я посчастливилось иметь дело, я не знаю, как объяснить:

-(RemoverBlock)whenSettledDo:(SettledHandlerBlock)settledHandler { 
    // without this local assignment of the argument, one of the tests fails. Why? 
    SettledHandler handlerFixed = settledHandler; 

    [removableSettledHandlers addObject:handlerFixed]; 

    return ^{ 
     [removableSettledHandlers removeObject:handlerFixed]; 
    }; 
} 

Что называется блочным рядный, как это:

-(void) whatever { 
    [self whenSettledDo:^(...){ 
     ... 
    }]; 
} 

(The actual code this snipper was adapted from is here.)

Что делает копирование аргумента в локальную переменную здесь? Является ли версия без локального создания двух разных копий, одна для addObject и одна для removeObject, поэтому удаленная копия не соответствует добавленной копии?

Почему и когда нет блоков обработки ARC правильно? Что это гарантирует и каковы мои обязанности? Где все это описано в нечеткой форме?

ответ

0

Я подтвердил, что моя конкретная проблема фактически создавала две отдельные копии блока. Трудно сложно. Это означает, что правильный совет «никогда не копируется, если вы не хотите сравнить блок с собой».

Вот код, который я использовал, чтобы проверить:

-(void) testMultipleCopyShenanigans { 
    NSMutableArray* blocks = [NSMutableArray array]; 
    NSObject* v = nil; 
    TOCCancelHandler remover = [self addAndReturnRemoverFor:^{ [v description]; } 
                 to:blocks]; 
    test(blocks.count == 1); 
    remover(); 
    test(blocks.count == 0); // <--- this test fails 
} 
-(void(^)(void))addAndReturnRemoverFor:(void(^)(void))block to:(NSMutableArray*)array { 
    NSLog(@"Argument: %@", block); 
    [array addObject:block]; 
    NSLog(@"Added___: %@", array.lastObject); 
    return ^{ 
     NSLog(@"Removing: %@", block); 
     [array removeObject:block]; 
    }; 
} 

Выход каротаж при выполнении этого теста является:

Argument: <__NSStackBlock__: 0xbffff220> 
Added___: <__NSMallocBlock__: 0x2e283d0> 
Removing: <__NSMallocBlock__: 0x2e27ed0> 

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

Таким образом, NSMallocBlock в массиве имеет адрес, заканчивающийся на 83d0, тогда как в закрытии, который удален из массива, имеет адрес, заканчивающийся на 7ed0. Они разные. Удаление одного из них не считается удалением другого.

Bleh, думаю, мне нужно следить за этим в будущем.

3

В 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:), если это блок буквальное, или это изменяемый параметр, который вам Проходит.

+0

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

+0

@Strilanc: Я думаю, что назначение локального типа указателя блока будет копировать его. Я не уверен, относится ли часть о «результат используется только как аргумент к вызову», когда вы передаете литерал блока напрямую или если он также включает локальный, который используется только в одном месте, в качестве аргумента , – newacct

0

Блок должен быть скопирован, когда приложение покидает область, в которой был определен блок. Плохой пример:

BOOL yesno; 
dispatch_block_t aBlock; 
if (yesno) 
{ 
    aBlock = ^(void) { printf ("yesno is true\n"); 
} 
else 
{ 
    aBlock = ^(void) { printf ("yesno is false\n"); 
} 
aBlock = [aBlock copy]; 

Слишком поздно! Блок оставил свой объем ({скобки)), и все может пойти не так. Это можно было бы зафиксировать тривиально, не имея {скобки}, но это один из редких случаев, когда вы вызываете копию самостоятельно.

Когда вы храните где-то блок, 99,99% времени, когда вы покидаете область, в которой был объявлен блок; обычно это решается путем создания свойств свойства «копировать». Если вы вызываете dispatch_async и т. Д., Блок нужно скопировать, но вызываемая функция сделает это. Итераторам на основе блоков для NSArray и NSDictionary обычно не нужно делать копии блока, потому что вы все еще работаете внутри области, где был объявлен блок.

[aBlock copy], когда блок уже был скопирован, ничего не делает, он просто возвращает сам блок.

Смежные вопросы