Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

does dispatch_async copy internal blocks

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?

like image 857
Awesome-o Avatar asked Aug 10 '13 00:08

Awesome-o


2 Answers

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.

enter image description here

like image 76
CouchDeveloper Avatar answered Oct 23 '22 06:10

CouchDeveloper


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

like image 39
JRG-Developer Avatar answered Oct 23 '22 07:10

JRG-Developer