块文字是否应保留引用的堆分配块

时间:2011-03-26 23:43:15

标签: objective-c objective-c-blocks

请考虑以下代码:

// 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](在非实例变量的情况下)。

另外,请注意,当复制block2时,对block1的引用也会更改为对block1的新副本(与block1_copied不同)的引用,因为块会复制它们引用的任何块[2 ]

那么,这里发生了什么?

A)如果块保留了它们所引用的objective-c对象,并且块是objective-c对象,为什么在block2超出范围之前取消分配block1_copied?

B)如果块复制它们引用的块,并且如果将 - (id)副本发送到堆分配块实际上只增加其保留计数,为什么在block2超出范围之前取消分配block1_copied?

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相同的内存地址。

2 个答案:

答案 0 :(得分:5)

This is the specification。它现在略显陈旧,没有正常规范的形式。但是,C工作组已经提出了块,并且在这种情况下讨论了更正式的规范。

具体来说,规范说:

  

Block_copy运算符保留所有   对象保存在自动变量中   块内引用的存储   表达(或形成强烈的参考   如果在垃圾收集下运行)。   __block存储的对象变量   假设类型保持正常   没有保留条款的指针   并发布消息。

因此,您所看到的行为是正确的,尽管这绝对是一个陷阱!

在复制块之前,块不会保留任何内容。就像从堆栈开始的块一样,这主要是基于性能的决策。

如果您要将代码更改为:

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

表现如预期。

静态分析器应该捕获它,但不会(请file a bug)。

答案 1 :(得分:1)

我注意到普通物体似乎也是如此。这段代码:

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尚未复制。