Objective-C中块变量捕获的规则

6
在Objective-C中,通过块捕获变量的语义是什么?
#import <Foundation/Foundation.h>

#include <stdio.h>

int main()
{
  NSMutableArray *arr = [NSMutableArray array];
  for (int i = 0; i < 100; ++i) {
    int j = i;
    [arr addObject:^(void) {printf("%d %d\n", i, j); }];
  }
  for (void (^blk)(void) in arr) {
    blk();
  }
}

我期望它可以打印出类似这样的内容:

100 0
100 1
...
100 99

相反,它会打印出:
99 99
99 99
...
99 99

它是如何将j解释为等于99的呢?j在for循环之外甚至不存在。


1
奇怪。我本来期望输出是 0 01 1,... 99 99 - rmaddy
1
这里有一个想法。在第二个for循环内部,记录blk的地址。实际上,您可能在数组中重复100次拥有同一个块,并且它们全部引用捕获循环最后值的最后一个块。 - rmaddy
@rmaddy: 你对第一个期望是正确的。https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html重要引用:“只有值被捕获,除非你另有指定。” - Brennan Vincent
我刚刚在一个简单的Cocoa命令行应用程序中测试了你的代码,我得到了我在第一条评论中期望的输出。我不知道你遇到了什么问题。 - rmaddy
@rmaddy请看Ryan的答案。这是因为我在编译时没有开启ARC。 - Brennan Vincent
是的,我也刚测试了一下。有趣。 - rmaddy
2个回答

6

因为你没有使用ARC!没有使用ARC,你的block不会被复制。你只是侥幸地每次都运行最后一个block。


5
您看到许多次的“99 99”是由于未定义行为引起的。
让我们来看一下第一个for循环:
for (int i = 0; i < 100; ++i) {
  int j = i;
  dispatch_block_t block = ^(void) {printf("%d %d\n", i, j); };
  [arr addObject:block];
}

[为了清晰起见,我已经提取出了该块。]

在这个for循环中,创建了该块。它是在堆栈上创建的,因为没有复制该块。

每次循环时,极有可能(实际上是确定的)使用相同的堆栈空间来创建该块。然后将该块的地址(位于堆栈上)添加到arr中。每次都是相同的地址。但每次都是新的块实现。

第一个for循环结束后,arr包含相同的值100次。该值指向最后创建的块,该块仍然位于堆栈上。但它指向了一个已经超出作用域无法安全访问的堆栈上的块。

然而,在这个例子中,由于简单的代码运行的幸运(好吧,简单的代码),该块所占用的堆栈空间尚未被重用。因此,当您使用该块时,它“有效”。

正确的解决方案是在块被添加到数组时进行复制。可以通过调用copy或让ARC为您完成来实现。这样,块将被复制到堆中,并且您将拥有一个引用计数块,它将根据数组和创建它的范围所需的时间而存活。
如果您想了解有关块的工作原理并更深入地理解此答案,则建议查看我的解释:

http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-1/ http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-2/ http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接