Objective-C中的block到底是个啥(一)

2017-11-09  本文已影响79人  d8893ea8ba05

现在GCD几乎已经是iOS中多线程编程的核心技术,而其中很重要的一个部分就是block的使用,那么用了这么多年的block,block究竟是个啥?最近通过阅读Matt Galloway的博客,现将对block的一些探究翻译并分享出来。

尝试从汇编的角度查看block是啥

首先,建立一个InsideBlock.m文件,代码如下:


typedef void(^BlockA)(void);

void runBlockA(BlockA block) {
    block();
}

void doBlockA() {
    BlockA block = ^{
        // Empty block
    };
    runBlockA(block);
}

然后使用如下命令,既可以得到该文件的汇编指令:
clang -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.0.sdk -arch armv7 -S -o3 InsideBlock.m -o InsideBlock.s
查看InsideBlock.s文件,从中我们可以先看到runBlock方法的汇编指令:

_runBlockA:
@ BB#0:
    push    {r7, lr}
    mov r7, sp
    sub sp, #8
    str r0, [sp, #4]
    ldr r0, [sp, #4]
    mov r1, r0
    ldr r0, [r0, #12]
    str r0, [sp]                @ 4-byte Spill
    mov r0, r1
    ldr r1, [sp]                @ 4-byte Reload
    blx r1
    add sp, #8
    pop {r7, pc}

    .globl  _doBlockA
    .p2align    1
    .code   16                      @ @doBlockA
    .thumb_func _doBlockA

我们不需要详细解读这段汇编,只需要大概了解意思即可,iOS arm汇编中通常用r0-r3表示参数,其中blx表示执行函数调用,也就是说上面的这段汇编单代码就是执行了传入的函数。
再往下是doBlockA的汇编指令:

_doBlockA:
@ BB#0:
    push    {r7, lr}
    mov r7, sp
    sub sp, #4
    movw    r0, :lower16:(___block_literal_global-(LPC1_0+4))
    movt    r0, :upper16:(___block_literal_global-(LPC1_0+4))
LPC1_0:
    add r0, pc
    str r0, [sp]
    ldr r0, [sp]
    bl  _runBlockA
    add sp, #4
    pop {r7, pc}

    .p2align    1
    .code   16                      @ @__doBlockA_block_invoke
    .thumb_func ___doBlockA_block_invoke

其中movw和movt是将指付给r0,bl调用了runBlockA方法,因为我们知道runBlockA方法需要一个block对象,因此我们断定:___block_literal_global就是这个block对象的表示。剩下的就是找到___block_literal_global的定义就可以知道block在汇编中是如何表示的了。继续向下找,可以找到:

___block_literal_global:
    .long   __NSConcreteGlobalBlock
    .long   1342177280              @ 0x50000000
    .long   0                       @ 0x0
    .long   ___doBlockA_block_invoke
    .long   ___block_descriptor_tmp

    .section    __DATA,__objc_imageinfo,regular,no_dead_strip

可以看到它是一个结构体,每个字段四个字节,最后两个字段___doBlockA_block_invoke和___block_descriptor_tmp的定义也可以从汇编中看得到定义:

___doBlockA_block_invoke:
@ BB#0:
    sub sp, #8
    str r0, [sp, #4]
    str r0, [sp]
    add sp, #8
    bx  lr

    .section    __TEXT,__cstring,cstring_literals
___block_descriptor_tmp:
    .long   0                       @ 0x0
    .long   20                      @ 0x14
    .long   L_.str
    .long   0

    .p2align    2               @ @__block_literal_global

现在block的结构式比较清楚了,一个结构体,其中里面还有两个字段指向另外的结构体。那么再具体点,我们可以通过查阅compiler-rt项目源码中的Block_private.h头文件,可以看到block里面具体是个啥:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

那么把所有对刚才汇编的分解综合起来,之前的object-c的block代码就可以用如下比较简单的伪代码来表示出来:

void runBlockA(struct Block_layout *block) {
    block->invoke();
}

void block_invoke(struct Block_layout *block) {
    // Empty block function
}

void doBlockA() {
    struct Block_descriptor descriptor;
    descriptor->reserved = 0;
    descriptor->size = 20;
    descriptor->copy = NULL;
    descriptor->dispose = NULL;

    struct Block_layout block;
    block->isa = _NSConcreteGlobalBlock;
    block->flags = 1342177280;
    block->reserved = 0;
    block->invoke = block_invoke;
    block->descriptor = descriptor;

    runBlockA(&block);
}

所以归根结底,block在内存中是一个Block_layout的结构体,包含了isa、flgas、reversed、invoke指针和一个descriptor指针

上一篇 下一篇

猜你喜欢

热点阅读