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会指向堆的结构体
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容