What are the semantics of capturing a variable by a block in 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();
}
}
I was expecing this to print something like:
100 0
100 1
...
100 99
Instead, it prints:
99 99
99 99
...
99 99
How is it even possible that it's interpreting j
as equal to 99
? j
isn't even alive outside the for loop.
Because you're not using ARC! Without it, your block isn't being copied. You're just getting lucky and running the very last block every single time.
The reason you're seeing 99 99
many times is simply due to undefined behaviour.
Let's take the first for-loop:
for (int i = 0; i < 100; ++i) {
int j = i;
dispatch_block_t block = ^(void) {printf("%d %d\n", i, j); };
[arr addObject:block];
}
[I've pulled out the block for clarity.]
Inside this for-loop, the block is created. It is created on the stack and never moved to the heap because there is no copy of the block to do so.
Each time around the for-loop, it's extremely likely (well, certain really) that the same stack space is used for the block. And then the address of the block (which is on the stack) is added to arr
. The same address each time. But it's a new implementation of the block each time.
Once out of the first for-loop, arr
contains the same value 100 times. And that value points to the last created block, which is still on the stack. But it's pointing to a block on the stack that can no longer be accessed safely because it's out of scope.
In the case of this example however, the stack space occupied by the block by sheer luck (OK, simple code) hasn't been reused. So when you go and use the block, it "works".
The correct solution is to copy the block when it is added to the array. Either by calling copy
or letting ARC do that for you. That way, the block is copied to the heap and you have a reference counted block that will live as long as needed by the array and the scope in which it is created.
If you want to learn more about how blocks work and understand this answer more deeply, then I suggest my explanations here:
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/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With