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