Objective-C阻塞引用宿主对象的闭包

时间:2011-05-18 23:05:13

标签: objective-c closures objective-c-blocks host-object

我一直在玩积木并遇到一种奇怪的行为。 这是接口/实现,它只保存一个能够执行它的块:

@interface TestClass : NSObject {
#if NS_BLOCKS_AVAILABLE
    void (^blk)(void);
#endif
}
- (id)initWithBlock:(void (^)(void))block;
- (void)exec;
@end

@implementation TestClass
#if NS_BLOCKS_AVAILABLE
- (id)initWithBlock:(void (^)(void))block {
    if ((self = [super init])) {
        blk = Block_copy(block);
    }
    return self;
}
- (void)exec {
    if (blk) blk();
}
- (void)dealloc {
    Block_release(blk);
    [super dealloc];
}
#endif
@end

当一个常规实例化并传递一个常规块时:

TestClass *test = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass");
}];
[test exec];
[test release];

使用参考正在创建的对象的块不会:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

错误是EXC_BAD_ACCESS,Block_copy上的堆栈跟踪(块); 调试器开启:0x000023b2< + 0050>添加$ 0x18,%esp

我一直在玩,并将分配代码移到初始化之上,它起作用了:

TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
    NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];

结合两个片段也是有效的:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

TestClass *test2 = [TestClass alloc];
test2 = [test2 initWithBlock:^{
    NSLog(@"TestClass %@", test2);
}];
[test2 exec];
[test2 release];

这里发生了什么?

2 个答案:

答案 0 :(得分:5)

在赋值表达式中,rvalue在分配给左值之前进行求值。

这意味着:

TestClass *test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];

执行以下操作序列。 编辑:正如Jonathan Grynspan所指出的那样,步骤1和2没有明确的顺序,因此可能会出现步骤2在步骤1之前执行的情况。

  1. +alloc发送至TestClass
  2. 创建一个引用test1的块,该块尚未初始化。 test1包含任意内存地址。
  3. -initWithBlock:发送到在步骤1中创建的对象。
  4. 将右值分配给test1
  5. 请注意,test1仅在步骤4之后指向有效对象。

    在:

    TestClass *test2 = [TestClass alloc];
    test2 = [test2 initWithBlock:^{
        NSLog(@"TestClass %@", test2);
    }];
    [test2 exec];
    [test2 release];
    

    序列是:

    1. +alloc发送至TestClass
    2. 将rvalue分配给test2,现在指向TestClass个对象。
    3. 创建一个引用test2的块,该块指向每个步骤2的TestClass对象。
    4. -initWithBlock:发送到test2,这是在步骤2中正确分配的。
    5. 将右值分配给test2

答案 1 :(得分:1)

问题是,当创建一个块时,它将复制(单独复制)它捕获的任何非__block变量。由于test1在创建块时未初始化,因此在运行块时,您将使用未初始化的指针test1

正确的解决方案是使用test1声明__block。这样,状态在块和封闭范围之间共享,并且在封闭范围中分配test1后,块可以访问更改的值:

__block TestClass *test1;
test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", test1);
}];
[test1 exec];
[test1 release];

P.S。第三个示例(之前执行alloc然后分配init的结果)是不可靠的,因为通常不保证对象的init方法返回它被调用的对象({如果失败,则允许{1}}解除分配并返回nil。例如)。


更新:上述代码仅适用于MRC,因为该块不会保留init个变量。

但是在ARC中,上面的代码会导致保留周期,因为__block对象指针变量默认由块保留。在ARC中,正确的代码是:

__block