为什么这个块在创建时不会捕获变量值

时间:2013-02-27 17:06:30

标签: objective-c objective-c-blocks dispatch-async

我在使用GCD的Objective-C中遇到以下问题我无法弄清楚:

我使用以下方法计算某些648个图块的内容。 首先处理瓦片的顺序由设置变量“pi”的一些算法给出。变量“loaded”在此上下文中是全局变量,从0开始,正确地上升到647。

不使用该块时,一切都很完美。

while (loaded < [self.tiles count]) {
    long pi = /* tricky way to calculate the position index to set */;
    NSLog(@"loaded: %d", loaded);
    // Do this in a separate thread
    dispatch_async(loader, ^{
        NSLog(@"loaded ->: %d", loaded);
        [self.tiles[loaded] setPositionIndexTo:pi];
    });

    loaded++;
}

问题:

我得到一个异常,因为该块试图访问self.tiles [648]! 在Apple的块文档中,可以看出,在创建块时,块中的变量值被捕获,所以我不明白,这是怎么回事。我确实理解加载的变量实际上最终确实具有值648,这应该在不执行的情况下中止循环。其他一些奇怪的是,值0也从未在块中使用,但它从1开始。有时也可能看到块在有关加载值的循环之前,有时会错过值,或做两次。这是一些输出:

 loaded ->: 612
 loaded: 613
 loaded ->: 613
 loaded ->: 614
 loaded: 614
 loaded ->: 614
 loaded: 615
 loaded: 616
 loaded ->: 615
 loaded: 617
 loaded ->: 617

为什么以及如何做到这一点?

感谢任何澄清,正如我认为Apple的文件明确指出的那样,当创建块时,应该捕获“已加载”的值,而不再对块进行更改。

2 个答案:

答案 0 :(得分:6)

问题是loaded是实例变量,而不是局部变量。块捕获本地范围。在Objective-C中,当您访问实例变量时,编译器会将该访问权转换为结构成员访问权。

@interface MyClass : NSObject
{
    int varA;
}
@end

@implementation MyClass
- (void)someMethod
{
    varA = 42; // This
    self->varA = 42 // is actually this after compilation
}
@end

因此,该块在运行时始终会看到加载的当前值,因为它实际上是在查看self->loaded

此问题有多种解决方案,但通常您需要确保每个块都看到loaded的唯一值。一个简单的方法是使loaded成为局部变量。如果需要跟踪整体进度,请从块中更新进度属性。 (请注意,如果loader是并发队列,则在更新progress属性时需要注意多线程问题。一个简单的解决方案是将更新分发回自定义串行队列。)

答案 1 :(得分:1)

封闭词法范围本地的所有Stack(非静态)变量都被捕获为const变量。  您的案例中的实例变量将作为普通变量访问。

要解决您的问题,请使用值为loaded的局部变量并将其用于块处理。

int index = loaded;

while (index < [self.tiles count]) {
    long pi = /* tricky way to calculate the position index to set */;
    NSLog(@"loaded: %d", index);
    // Do this in a separate thread
    dispatch_async(loader, ^{
        NSLog(@"loaded ->: %d", index);
        [self.tiles[index] setPositionIndexTo:pi];
    });

    index++;
}