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

ela*_*ado 2 closures objective-c 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
Run Code Online (Sandbox Code Playgroud)

虽然常规实例化并传递常规块工作:

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

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

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

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

我一直在玩,并将分配代码移到初始化之上,它有效:

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

并结合两个片段也有效:

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];
Run Code Online (Sandbox Code Playgroud)

这里发生了什么?

小智 5

在赋值表达式中,在分配给左值之前评估rvalue.

这意味着:

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

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

  1. 发送+allocTestClass
  2. 创建一个引用的块,该块test1尚未初始化.test1包含任意内存地址.
  3. 发送-initWithBlock:到步骤1中创建的对象.
  4. 将rvalue分配给test1.

请注意,test1仅在步骤4之后指向有效对象.

在:

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

顺序是:

  1. 发送+allocTestClass
  2. 将rvalue分配给test2,现在指向一个TestClass对象.
  3. 创建一个引用的块test2,它指向TestClass每个步骤2 的对象.
  4. 发送-initWithBlock:test2,在步骤2中正确分配.
  5. 将rvalue分配给test2.

  • 实际上,`[TestClass alloc]`和`^ {...}`之间的评估顺序是未定义的.在C级别,两者都被计算为`-initWithBlock:`的参数,因此在调用*`[TestClass alloc]`之前实际上可以创建块.:) (2认同)