【问题标题】:does dispatch_async copy internal blocksdispatch_async 是否复制内部块
【发布时间】:2013-08-10 00:06:52
【问题描述】:

鉴于以下情况(手动引用计数):

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

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

dispatch_async(dispatch_get_main_queue(), async_block);

“块”会被复制而不是从堆栈中丢弃并销毁吗?

【问题讨论】:

    标签: ios objective-c objective-c-blocks reference-counting


    【解决方案1】:

    我相信,答案是肯定的。

    外部块将被异步分派,这会导致运行时在堆上为此块制作副本。并且如下图,在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复制到堆中。

    【讨论】:

      【解决方案2】:

      来自Apple docs 上的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 被复制了。

      每个 this discussionblock (在您的示例中位于 async_block 内部)将是 readonly copy 内部 async_block

      【讨论】:

        【解决方案3】:

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

        【讨论】:

        • 您从哪里获得这些信息的?保留一个块有什么作用?
        • Blocks 继承自 NSObject(分别是 Apple _NSBlock 和 GNUstep GSBlock 类,两者都是私有的,但是唯一的例外:LLVM 在 compiler-rt 中的实现使用了基于 C 的方法,即与 CoreFoundation 惊人地相似),所以当他们被困在另一个街区时,他们得到了NSArrays 和NSStrings 的NSStrings
        • @Gabriele Petronella 从 iOS 6.0 和 Mac OS X 10.8 开始,Maxthon 说块是“对象”是正确的。 ARC 将它们作为可保留的对象指针进行管理。但是,这并不意味着自动复制块。在该示例中,即使在 mac OS X 10.8 或 iOS 6 和启用 ARC 上,也需要显式复制块 block 以便将其转移到堆上。否则,block 会留在栈中,异步调用的块async_block 会崩溃。
        • 我必须修改我最后的声明,即块 block 不会自动复制。请看我的回答。
        • CouchDeveloper 发现,块内的块将被复制。虽然块确实是对象,但它们有时会像这种情况一样接受特殊处理。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多