Reputation: 2737
I have the following problem in Objective-C using GCD I cannot figure out:
I am using the following method to calculate something for some 648 tiles. The order of which tiles to process first is given by some algorithm setting the variable "pi". The variable "loaded" is global in this context and starts at 0 and correctly goes up to 647.
Everything works perfectly when not using the block.
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++;
}
Problem:
I get an exception because the block tries to access self.tiles[648]! In Apple's block documentation it sais, that the value of a variable inside the block is captured when the block is created, so I don't understand, how this is even possible. I do understand that the variable loaded in fact does have the value 648 in the end, which should abort the loop without executing. Some other weirdness is, that the value 0 is also never used in the block, but it starts out with 1. It is also sometimes possible to see that the block is ahead of the loop concerning the value of loaded, sometimes misses values at all, or does some twice. Here is some output:
loaded ->: 612
loaded: 613
loaded ->: 613
loaded ->: 614
loaded: 614
loaded ->: 614
loaded: 615
loaded: 616
loaded ->: 615
loaded: 617
loaded ->: 617
Why and how is this possible?
Thanks for any clarification, as I think Apple's documents clearly state, that when the block is created, the value of "loaded" should be captured without being altered for the block anymore.
Upvotes: 2
Views: 1157
Reputation: 21373
The problem is that loaded
is an instance variable, not a local variable. Blocks capture local scope. In Objective-C, when you access an instance variable, the compiler converts that access to a struct member access.
@interface MyClass : NSObject
{
int varA;
}
@end
@implementation MyClass
- (void)someMethod
{
varA = 42; // This
self->varA = 42 // is actually this after compilation
}
@end
So, the block will always see the current value of loaded when it is run, because it's actually looking at self->loaded
.
There are multiple solutions to this problem, but in general you need to make sure that each block sees a unique value for loaded
. An easy way to do this would be to make loaded
a local variable. If you need to track overall progress, update a progress property from the block. (Note that if loader
is a concurrent queue, you'll need to be careful about multithreading issues when updating the progress property. An easy solution is to dispatch the update back to a custom serial queue.)
Upvotes: 7
Reputation: 1229
All Stack (non-static) variables local to the enclosing lexical scope are captured as const variables. Instance variables as in your case will be accessed as normal variables.
To solve your problem, take a local variable with a value of loaded and use it for block processing.
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++;
}
Upvotes: 1