5

给定以下(手动引用计数):

void (^block)(void) = ^ {
    NSLog(@"wuttup");
}

void (^async_block)(void) = ^ {
    block();
}

dispatch_async(dispatch_get_main_queue(), async_block);

“块”会被复制而不是被扔出堆栈并被销毁吗?

4

3 回答 3

8

我相信,答案是肯定的。

外部块将被异步调度,这会导致运行时在堆上为此块制作副本。如下所示,并在Block Implementation Specification - Clang 3.4 Documentation中进行了描述,内部块的导入变量也被复制到堆中。

在 OP 的示例中,我们有一个“块引用的导入 const 副本”。

我正在使用规范中的示例:

void (^existingBlock)(void) = ...;
void (^vv)(void) = ^{ existingBlock(); }
vv();

规范指出需要copy_helperdispose_helper功能:

copy_helper 函数同时传递现有的基于堆栈的指针和指向新堆版本的指针,并应回调到运行时以实际对块内的导入字段执行复制操作。

规范中的以下示例代码难以解读(实际上缺少将外部块复制到堆时发生的情况的描述)。无论如何,规范似乎试图表明内部块的导入变量将(递归地)复制到外部块的原始存储区域中。

当外部块将被复制到堆上时,看起来内部块的导入变量最终也会存在于堆上。

嗯,直觉上,这一切都是有道理的。

我做了一个小测试程序来证明这一点:(你必须调试和检查反汇编才能弄清楚表面下发生了什么)。

#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;
}

输出如下:

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

当块在主线程上执行时,它位于堆栈中(如本地变量和导入变量的地址所示)。当通过运行时执行dispatch_async时,复制了块 - 包括内部块,从块的本地和导入变量的地址可以看出。

我们可以在函数处设置一个断点,copy_helper_block实际上,程序会停在那里一次,以便将块复制vv到堆中。

在此处输入图像描述

于 2013-08-10T08:41:17.930 回答
5

来自Apple文档dispatch_async

堵塞

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.

所以,async_block被复制了。

根据这个讨论block(在你的例子中async_block),将是一个insidereadonly copyasync_block

于 2013-08-10T01:16:13.817 回答
0

blockretained(不是按 sé 复制)因为async_block像普通对象一样被捕获在另一个块中。块的复制是块对象被发送[block retain]消息的结果,该消息称为复制块的覆盖retain方法。

于 2013-08-10T01:15:49.757 回答