Given the following (manual reference counting):
void (^block)(void) = ^ {
NSLog(@"wuttup");
}
void (^async_block)(void) = ^ {
block();
}
dispatch_async(dispatch_get_main_queue(), async_block);
Will "block" be copied rather than thrown off the stack and destroyed?
I believe, the answer is Yes.
The outer block will be asynchronously dispatched which causes the runtime to make a copy on the heap for this block. And as shown below, and described in the Block Implementation Specification - Clang 3.4 Documentation, the inner block's imported variables are also copied to the heap.
In the OP's example we have a "imported const copy of a Block reference".
I'm using the example in the Specification:
void (^existingBlock)(void) = ...;
void (^vv)(void) = ^{ existingBlock(); }
vv();
The Specification states that the copy_helper
and dispose_helper
functions are needed:
The copy_helper function is passed both the existing stack based pointer and the pointer to the new heap version and should call back into the runtime to actually do the copy operation on the imported fields within the Block.
The following example code in the Specification is difficult to decipher (and actually lacks the description what happens when the outer block is copied to the heap). Anyway, it appears the specification tries to show that imported variables of inner blocks will be (recursively) copied into the raw storage area of the outer block.
When the outer block will be copied on the heap, it seems imported variables of inner blocks will eventually live on the heap as well.
Well, intuitively, this all makes sense.
I made a small test program which will demonstrate this: (you have to debug and examine the disassembly in order to figure out whats going on under the surface).
#import <Foundation/Foundation.h>
void foo(int param)
{
int x0 = param;
int x1 = param + 1;
void (^existingBlock)(void) = ^{
int y0 = x0;
int y1 = x1;
printf("&y0: %p\n", &y0);
printf("&y1: %p\n", &y1);
printf("&x0: %p\n", &x0);
printf("&x1: %p\n", &x1);
};
void (^vv)(void) = ^{
int y2 = x0;
int y3 = x1;
existingBlock();
printf("&y2: %p\n", &y2);
printf("&y3: %p\n", &y3);
printf("&x0: %p\n", &x0);
printf("&x1: %p\n", &x1);
};
printf("Stack: &x: %p\n", &x0);
printf("Stack: &x: %p\n", &x1);
printf("------- on main thread -------\n");
vv();
dispatch_async(dispatch_get_global_queue(0, 0), ^{
printf("------- on thread 2 -------\n");
assert(vv);
sleep(1);
int y4 = x0;
int y5 = x1;
vv();
printf("&y4: %p\n", &y4);
printf("&y5: %p\n", &y5);
printf("&x0: %p\n", &x0);
printf("&x1: %p\n", &x1);
});
}
int main(int argc, const char * argv[])
{
@autoreleasepool {
foo(1);
sleep(2);
}
return 0;
}
The output is as follows:
Stack: &x: 0x7fff5fbff868
Stack: &x: 0x7fff5fbff864
------- on main thread -------
&y0: 0x7fff5fbff70c
&y1: 0x7fff5fbff708
&x0: 0x1001081e0
&x1: 0x1001081e4
&y2: 0x7fff5fbff76c
&y3: 0x7fff5fbff768
&x0: 0x10010a588
&x1: 0x10010a58c
------- on thread 2 -------
&y0: 0x1000e5d9c
&y1: 0x1000e5d98
&x0: 0x1001081e0
&x1: 0x1001081e4
&y2: 0x1000e5dfc
&y3: 0x1000e5df8
&x0: 0x10010a588
&x1: 0x10010a58c
&y4: 0x1000e5e6c
&y5: 0x1000e5e68
&x0: 0x10010a5e8
&x1: 0x10010a5ec
When the block is executed on the main thread, it lives on the stack (as shown by the addresses of the local and imported variables). When executed via dispatch_async
the runtime has copied the block - including the inner blocks, as can be seen by the addresses of the local and imported variables of the blocks.
We can set a breakpoint at the copy_helper_block
function, and in fact, the program stops there once, in order to copy the block vv
to the heap.
From the Apple docs on dispatch_async
:
block
The block to submit to the target dispatch queue. This function performs Block_copy and Block_release on behalf of callers. This parameter cannot be NULL.
So, async_block
is copied.
Per this discussion , block
(inside of async_block
in your example), will be a readonly copy
within async_block
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