2

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

@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(block) 上的堆栈跟踪;调试器开启:0x000023b2 <+0050> add $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];

这里发生了什么?

4

2 回答 2

5

在赋值表达式中,右值在被赋值给左值之前被求值。

这意味着在:

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

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

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

请注意,test1仅在第 4 步之后才指向有效对象。

在:

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

顺序是:

  1. 发送+allocTestClass
  2. 将右值分配给test2,它现在指向一个TestClass对象。
  3. 创建一个引用 的块test2,它在步骤 2 中指向TestClass对象。
  4. 发送-initWithBlock:test2,在步骤 2 中已正确分配。
  5. 将右值分配给test2
于 2011-05-18T23:13:08.513 回答
1

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

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

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

ps 第三个示例(先执行alloc然后分配 的结果init)不可靠,因为通常init不能保证对象的方法返回调用它的对象(init例如,允许释放自身并在失败时返回 nil) .


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

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

TestClass *test1;
__block __weak TestClass *weakTest1;
weakTest1 = test1 = [[TestClass alloc] initWithBlock:^{
    NSLog(@"TestClass %@", weakTest1);
}];
[test1 exec];
于 2011-09-16T22:55:43.737 回答