Подтвердить что ты не робот

Если в блочном литере сохраняются ссылки на блоки, выделенные кучей

Рассмотрим следующий код:

// t included so block1 is a stack block. See [1] below
int t = 1;
SimpleBlock block1 = ^{ NSLog(@"block1, %d", t); };

// copy block1 to the heap
SimpleBlock block1_copied = [block1 copy];

// block2 is allocated on the stack, and refers to
// block1 on the stack and block1_copied on the heap
SimpleBlock block2 = ^{
    NSLog(@"block2");
    block1_copied();
    block1();
};
[block1_copied release];

// When the next line of code is executed, block2_copied is
// allocated at the same memory address on on the heap as
// block1_copied, indicating that block1_copied has been
// deallocated. Why didn't block2 retain block1_copied?

SimpleBlock block2_copied = [block2 copy];
block2_copied();
[block2_copied release];

Где, для полноты, SimpleBlock определяется:

typedef void (^SimpleBlock)(void);

Как указано в комментарии в коде, мои тесты (с использованием как GCC 4.2, так и LLVM 2.0) показывают, что block1_copied освобождается к тому времени, когда вызывается [block2 copy], но в соответствии с документацией, которую я прочитал [1, 3], блоки представляют собой объекты objective-c и блоки сохраняют objective-c объекты, к которым они ссылаются [2] (в случае переменной без экземпляра).

Кроме того, обратите внимание, что при копировании блока 2 его ссылка на блок1 также изменяется на ссылку на новую копию блока1 (которая отличается от block1_copied), как и ожидалось, поскольку блоки копируют любые блоки, к которым они относятся [2 ].

Итак, что здесь происходит?

A) Если блоки сохраняют objective-c объекты, к которым они относятся, а блоки - это объекты objective-c, почему block1_copied освобождается до того, как block2 выходит из области видимости?

B) Если блоки блокировать копии, к которым они относятся, и если отправка - (id) копия в блок, выделенный для кучи, фактически просто увеличивает его количество удержания, почему block1_copied освобождается до того, как block2 выходит из области видимости?

C) Если это ожидаемое поведение, где это документация, которая его объясняет?

[1] http://cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html
[2] http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html
[3] http://clang.llvm.org/docs/BlockLanguageSpec.txt

Сноска: В моих тестах результат запуска этого кода является бесконечно рекурсивным вызовом block2_copied(), поскольку block1_copied() имеет тот же адрес памяти, что и block2_copied.

4b9b3361

Ответ 1

Это спецификация. Он немного устарел прямо сейчас и не имеет формализма нормальной спецификации. Однако в рабочей группе С были предложены блоки, и в этом контексте обсуждалась более формальная спецификация.

В частности, спецификация говорит:

Оператор Block_copy сохраняет все объекты, хранящиеся в переменных хранилище, указанное в блоке выражение (или сформировать сильные ссылки если работает под сборкой мусора). Переменные объекта __block storage предполагается, что тип указатели, не предусматривающие сохранения и сообщения о выпуске.

Таким образом, поведение, которое вы видите, является правильным, хотя это определенно ловушка!

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

Если вы хотите изменить свой код на:

SimpleBlock block2_copied = [block2 copy];
[block1_copied release];

Он ведет себя так, как ожидалось.

Статический анализатор должен поймать это, но не (указать ошибку).

Ответ 2

Я отмечаю, что то же самое происходит с обычными объектами. Этот код:

NSNumber *foo = [[NSNumber alloc] initWithInt:42];
void(^block)(void) = ^{ NSLog(@"foo = %@", foo); };
[foo release];
NSNumber *foo2 = [[NSNumber alloc] initWithInt:43];
void(^block_copy)(void) = [block copy];
block_copy();

Печать "foo = 43"

Можно было ожидать поведения. Чтобы привести документацию Apple:

При копировании блока любые ссылки на другие блоки из этого блока копируются при необходимости

В момент отпускания block1_copy block2 еще не скопирован.