Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 640
Gabriele Petronella Avatar asked Oct 05 '13 18:10

Gabriele Petronella


1 Answers

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.

like image 155
Gabriele Petronella Avatar answered Nov 18 '22 16:11

Gabriele Petronella