2

Please take a look at this piece of code:

NSMutableArray *array = [NSMutableArray array];

for (int i = 0; i < 10; i++)
{
    void (^b)() = ^{printf("%d\n", i);};
    [array addObject:b];
}

for (id obj in array)
{
    void(^b)() = obj;
    b();
}

[array removeAllObjects];

I expected this code to output 0, 1, 2 and so on but it prints 9 always. But why? Doesn't it capture i on every loop iteration? Why the last value is always captured? But what is more confusing for me is that if i change this line:

 void (^b)() = ^{printf("%d\n", i);};

to

void (^b)() = [^{printf("%d\n", i);} copy];

then it starts printing 0, 1, 2 and so on. Can anybody please explain why it works this way?

Andrey Chernukha
  • 21,488
  • 17
  • 97
  • 161

1 Answers1

4

This is not a problem with what the block captures, but rather with what block gets stored in the array. If you print addresses of the blocks after the first loop, you should see identical addresses being printed:

for (id obj in array)
{
    printf("%p\n", (void*)obj);
}

This is because all ten blocks are created on the stack in a loop, and are placed at the same address. Once the loop is over, the block created inside it is out of scope. Referencing it is undefined behavior. However, now that you stored the address of the block, you have a way to reference it (illegally).

Since the last block that was created in your loop has captured the last value of i (which is nine) all invocations of your block from your second loop would invoke the same block that prints nine.

This behavior changes if you copy the block, because now it is completely legal to reference it outside the scope where it has been created. Now all your blocks have different addresses, and so each one prints its own different number.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    +1 If you're keeping a block past the end of its scope, you need to copy it. With ARC, it's copied unconditionally. Without ARC, you either have to use a block-aware API that knows to copy the block (e.g. `dispatch_async()`) or you have to copy it yourself (and release the copy to balance). – Ken Thomases Jan 26 '14 at 15:25
  • @KenThomases: "With ARC, it's copied unconditionally." It's copied in this case because it's assigned to a `__strong` variable of block pointer type, `b`. But if the block literal is passed directly to `addObject:`, the ARC specification does not guarantee that it will be copied. – newacct Jan 27 '14 at 04:13