2

我在使用 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 的文件明确指出的那样,当创建块时,应该捕获“已加载”的值,而不再为该块进行更改。

4

2 回答 2

7

问题是这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

因此,该块在运行时总是会看到 load 的当前值,因为它实际上是在查看self->loaded.

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

于 2013-02-27T18:28:44.520 回答
1

封闭词法范围的所有本地堆栈(非静态)变量都被捕获为 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++;
}
于 2013-02-27T18:35:27.300 回答