Gabriele Petronella
Gabriele Petronella

Reputation: 108159

Why does ARC copy this block to the heap?

Consider this simple example

int i = 42;
int (^aBlock)() = ^ {
    return i;
};
NSLog(@"Class: %@", [aBlock class]);

Without ARC the above code prints

Class: __NSStackBlock__

whereas with ARC it prints

Class: __NSMallocBlock__

I placed a symbolic breakpoint on _Block_copy and it looks like ARC is inserting a Block_Copy() call, causing the block to be moved to the heap

It seems an unnecessary overhead and it defeats the whole purpose of having blocks on the stack in the first place.

Is this a limitation of ARC or is it a design choice?

Upvotes: 10

Views: 1372

Answers (1)

Gabriele Petronella
Gabriele Petronella

Reputation: 108159

Block pointers types are considered retainable object pointers types by ARC and such types - in absence of explicit ownership qualifier - are implicitly assumed to have a __strong qualifiers, as per the documentation:

If an object is declared with retainable object owner type, but without an explicit ownership qualifier, its type is implicitly adjusted to have __strong qualification.

So the above example is equivalent to

int i = 42;
__strong int (^aBlock)() = ^ {
    return i;
};
NSLog(@"Class: %@", [aBlock class]);

The documentation also states:

For __strong objects, the new pointee is first retained; second, the lvalue is loaded with primitive semantics; third, the new pointee is stored into the lvalue with primitive semantics; and finally, the old pointee is released.

and later

[...] whenever these semantics call for retaining a value of block-pointer type, it has the effect of a Block_copy [...]

So yes, ARC introduces a Block_copy call whenever assigning a block to a variable, since the variable is implicitly assumed considered to be __strong-qualified.

Skipping the assignment will keep the block on the stack. Consider the following example:

NSLog(@"Class: %@", [^int{return i;} class]); // => Class: __NSStackBlock__

The documentation also tells us that

The optimizer may remove such copies when it sees that the result is used only as an argument to a call.

And indeed it does. As proposed by Catfish_Man, turning the optimizations on (in this case by using the Release build configuration) will strip away the Block_Copy call, leaving the block on the stack.

Upvotes: 13

Related Questions