Objective-C Block底层原理

Block的内存结构

苹果官方文档中,给出了block的结构体定义:

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
    unsigned long int reserved;         // NULL
        unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

Block是如何捕获参数的:

OC代码如下:(为了代码的简洁,删掉了autoreleasepool相关代码)

int main(int argc, const char * argv[]) {
    int variable = 0x12345678;
    void (^block)(void) = ^{
        printf("var = %x\n", variable);
    };
    block();
    return 0;
}

可以使用下面的命令将OC代码转化为C++代码:

$ clang -rewrite-objc main.m -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int variable;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _variable, int flags=0) : variable(_variable) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int variable = __cself->variable; // bound by copy

        printf("var = %x\n", variable);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    int variable = 0x12345678;
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, variable));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

大多数人的误区

根据int variable = __cself->variable,variable是保存在栈区的。

其实非也。生成C++代码,是我们强制生成而已。正常编译的时候,OC是直接编译成机器指令的,并没有这一步骤。variable实际上是在block结构体所在的内存后面的。
编译后用Hopper查看:

                     _main:
0000000100000ef9         push       rbp
0000000100000efa         mov        rbp, rsp
0000000100000efd         push       rbx
0000000100000efe         sub        rsp, 0x28
0000000100000f02         mov        rax, qword [__NSConcreteStackBlock_100001010]
0000000100000f09         lea        rdi, qword [rbp+var_30]
0000000100000f0d         mov        qword [rdi], rax ;isa = _NSConcreteStackBlock
0000000100000f10         mov        eax, 0xc0000000
0000000100000f15         mov        qword [rdi+8], rax ;flags = 0xc0000000
0000000100000f19         lea        rax, qword [___main_block_invoke]
0000000100000f20         mov        qword [rdi+0x10], rax ;invoke = ___main_block_invoke
0000000100000f24         lea        rax, qword [___block_descriptor_tmp]
0000000100000f2b         mov        qword [rdi+0x18], rax ; descriptor = ___block_descriptor_tmp
0000000100000f2f         mov        dword [rdi+0x20], 0x12345678                ; argument "instance" for method imp___stubs__objc_retainBlock
0000000100000f36         call       imp___stubs__objc_retainBlock
0000000100000f3b         mov        rbx, rax
0000000100000f3e         mov        rdi, rbx
0000000100000f41         call       qword [rbx+0x10]
0000000100000f44         mov        rdi, rbx                                    ; argument "instance" for method _objc_release
0000000100000f47         call       qword [_objc_release_100001018]             ; _objc_release
0000000100000f4d         xor        eax, eax
0000000100000f4f         add        rsp, 0x28
0000000100000f53         pop        rbx
0000000100000f54         pop        rbp
0000000100000f55         ret

                     ___main_block_invoke:
0000000100000f56         push       rbp                                         ; DATA XREF=_main+32
0000000100000f57         mov        rbp, rsp
0000000100000f5a         mov        esi, dword [rdi+0x20] ; variable = dword [rdi+0x20] 
0000000100000f5d         lea        rdi, qword [0x100000f9c]                    ; "var = %x\\n", argument "format" for method imp___stubs__printf
0000000100000f64         xor        eax, eax
0000000100000f66         pop        rbp
0000000100000f67         jmp        imp___stubs__printf

通过汇编代码,可以知道block的创建流程是这样子的:

  • 在栈上开辟了一块内存,用于存放Block的结构体;
  • 将Block的isa赋值为_NSConcreteStackBlock;
  • mov dword [rdi+0x20], 0x12345678可以看出,要传递的参数,是紧挨着Block_literal_1结构保存的。解释了为什么在block外对参数操作,不会影响到block;
  • 赋值操作,ARC情况下,会给我们加上objc_retainBlock的操作;
  • objc_retainBlock函数跟进去,是_Block_copy函数;
  • _Block_copy再跟进去,看到malloc了一块内存,再通过memmove把传进来的block结构体复制到堆上,然后堆上的block的isa指针指向_NSConcreteMallocBlock;
  • dword [rdi+0x20]这条指令取出block参数,可以看出参数并没有像C++代码那样赋值给一个栈变量。

__block的底层实现

在原来的代码上,加上__block的编译指令:

int main(int argc, const char * argv[]) {
    __block int variable = 0x12345678;
    void (^block)(void) = ^{
        printf("var = %x\n", variable);
    };
    block();
    return 0;
}
                     _main:
0000000100000e17         push       rbp
0000000100000e18         mov        rbp, rsp
0000000100000e1b         push       rbx
0000000100000e1c         sub        rsp, 0x48
0000000100000e20         lea        rax, qword [rbp+var_28]
0000000100000e24         mov        qword [rax], 0x0
0000000100000e2b         mov        qword [rax+8], rax
0000000100000e2f         movabs     rcx, 0x2020000000
0000000100000e39         mov        qword [rax+0x10], rcx
0000000100000e3d         mov        rcx, qword [__NSConcreteStackBlock_100001010]
0000000100000e44         lea        rdi, qword [rbp+var_50]
0000000100000e48         mov        qword [rdi], rcx
0000000100000e4b         mov        dword [rax+0x18], 0x12345678 ; 参数用一个结构体包了起来
0000000100000e52         mov        ecx, 0xc2000000
0000000100000e57         mov        qword [rdi+8], rcx
0000000100000e5b         lea        rcx, qword [___main_block_invoke]
0000000100000e62         mov        qword [rdi+0x10], rcx
0000000100000e66         lea        rcx, qword [___block_descriptor_tmp]
0000000100000e6d         mov        qword [rdi+0x18], rcx
0000000100000e71         mov        qword [rdi+0x20], rax                       ; argument "instance" for method imp___stubs__objc_retainBlock
0000000100000e75         call       imp___stubs__objc_retainBlock
0000000100000e7a         mov        rbx, rax
0000000100000e7d         mov        rdi, rbx
0000000100000e80         call       qword [rbx+0x10]
0000000100000e83         mov        rdi, rbx                                    ; argument "instance" for method _objc_release
0000000100000e86         call       qword [_objc_release_100001020]             ; _objc_release
0000000100000e8c         lea        rdi, qword [rbp+var_28]                     ; argument #1 for method imp___stubs___Block_object_dispose
0000000100000e90         mov        esi, 0x8                                    ; argument #2 for method imp___stubs___Block_object_dispose
0000000100000e95         call       imp___stubs___Block_object_dispose
0000000100000e9a         xor        eax, eax
0000000100000e9c         add        rsp, 0x48
0000000100000ea0         pop        rbx
0000000100000ea1         pop        rbp
0000000100000ea2         ret
                        ; endp
0000000100000ea3         mov        rbx, rax
0000000100000ea6         lea        rdi, qword [rbp-0x28]
0000000100000eaa         mov        esi, 0x8
0000000100000eaf         call       imp___stubs___Block_object_dispose
0000000100000eb4         mov        rdi, rbx
0000000100000eb7         call       imp___stubs___Unwind_Resume
0000000100000ebc         ud2

                     ___main_block_invoke:
0000000100000ebe         push       rbp                                         ; DATA XREF=_main+68
0000000100000ebf         mov        rbp, rsp
0000000100000ec2         mov        rax, qword [rdi+0x20]
0000000100000ec6         mov        rax, qword [rax+8]
0000000100000eca         mov        esi, dword [rax+0x18]
0000000100000ecd         lea        rdi, qword [0x100000f94]                    ; "var = %d\\n", argument "format" for method imp___stubs__printf
0000000100000ed4         xor        eax, eax
0000000100000ed6         pop        rbp
0000000100000ed7         jmp        imp___stubs__printf
  • mov dword [rax+0x18], 0x12345678,可以看出,参数被传进结构体里了;
  • mov qword [rdi+0x20], rax,参数结构体的指针传递到block结构体;
  • 参数结构体在blockcopy的时候会复制到堆上,然后栈上结构体的forwarding会指向堆的结构体

猜你喜欢

转载自blog.csdn.net/weixin_34321753/article/details/87019175