探寻Block的本质(1)—— 基本认识

block是什么

通俗的理解:block就是将一些代码封装起来,以便在将来某个时候被使用,如果你不去调用block,block内部封装的代码就不会执行。举一个简单的例子,下面在main函数中定义一个最简单的block

    @autoreleasepool {
        ^{
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
        }; 
    return 0;
}

**********************   运行结果   ************************
Program ended with exit code: 0

运行程序运行可以看到block内的代码是没有运行的,因为没有调用。Block的使用也很简单,可以像函数一样被使用。加上()就代表调用,如下

    @autoreleasepool {
        ^{
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
        }(); 
    return 0;
}

**********************   运行结果   ************************
2019-05-28 17:18:56.992746+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992924+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992939+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992947+0800 Interview03-block[2640:180864] I am a block!
Program ended with exit code: 0

如果上面写的太简练不习惯的话,通常大家可能是这么写

    @autoreleasepool {
        void (^block)(void) = ^{
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
        };
        block();

**********************   运行结果   ************************
2019-05-28 17:18:56.992746+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992924+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992939+0800 Interview03-block[2640:180864] I am a block!
2019-05-28 17:18:56.992947+0800 Interview03-block[2640:180864] I am a block!
Program ended with exit code: 0

至于block的书写语法请自行搞定。

你可能在面试中被这么问过:Block的本质是什么?

显然,仅仅通过上面的知识,肯定不会让面试官满意的。这里介绍一张block的底层结构图



关于此图有如下二点解释:

  • block的本质也是一个OC对象,它内部也有个isa指针
  • block是封装了函数调用函数调用环境的OC对象

第一点很容易看出,block地层结构图中的第一个成员就是一个isa指针,所以我们可以将block当成一个对象来看待。那么第二点中的 函数调用函数调用环境是什么意思呢?我们一步一步来。
我们先将上面的代码扩展一下

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10;
        void (^block)(int, int) = ^(int c, int b){
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"c = %d",c);
            NSLog(@"b = %d",b);
            NSLog(@"a的值为%d",a);
        };
        block(50,100);
    }
    return 0;
}

**********************   运行结果   ************************
2019-05-29 17:13:56.021422+0800 Interview03-block[9455:836127] I am a block!
2019-05-29 17:13:56.021630+0800 Interview03-block[9455:836127] I am a block!
2019-05-29 17:13:56.021639+0800 Interview03-block[9455:836127] c = 50
2019-05-29 17:13:56.021649+0800 Interview03-block[9455:836127] b = 100
2019-05-29 17:13:56.021700+0800 Interview03-block[9455:836127] a的值为10
Program ended with exit code: 0

上面的代码可以看出,block里面使用了它上面的 int a = 10 ,可以将这个先简单的理解成函数调用环境,顾名思义,就是block所用到的一些外部变量。
函数调用指的就是上面包含那5句打印代码的匿名函数,block被调用的时候,该函数就会被调用。
接下来,我们在命令行中,使用

 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

将main.m文件转换成C++中间代码,来仔细研究一下block的底层构造。在main.cpp文件中,将代码直接拉到底部,可以发现我们需要的内容。核心代码整理后如下,下面的代码展示了运行状态下block内的实际内容

#import <Foundation/Foundation.h>

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        
        int a = 10;
        
        void (^block)(int, int) = ^(int c, int b){
            NSLog(@"I am a block!");
            NSLog(@"I am a block!");
            NSLog(@"c = %d",c);
            NSLog(@"b = %d",b);
            NSLog(@"a的值为%d",a);
            
        };
        
        struct __main_block_impl_0 *tmpBlock = (__bridge struct __main_block_impl_0 *)block;
        
        block(50,100);
        

    }
    return 0;
}

我们将block的底层结构struct __main_block_impl_0直接般到main.m里面,同时,还需要将struct __block_impl以及struct __main_block_desc_0也搬过来,以保证编译正确,这样,我们便可以通过struct __main_block_impl_0来读取运行时下,block中的内容

struct __main_block_impl_0 *tmpBlock = (__bridge struct __main_block_impl_0 *)block;

我们可以在调试窗口看到如下信息

运行时下block内部的信息

接下来我们在block内部的代码段加上断点

然后通过Debug-->DebugWorkflow-->Always Show Disassembly查看一下此时的汇编代码

Interview03-block`__main_block_invoke:
    0x100000e90 <+0>:   pushq  %rbp
    0x100000e91 <+1>:   movq   %rsp, %rbp
    0x100000e94 <+4>:   subq   $0x20, %rsp
    0x100000e98 <+8>:   leaq   0x1c1(%rip), %rax         ; @"I am a block!"
    0x100000e9f <+15>:  movq   %rdi, -0x8(%rbp)
    0x100000ea3 <+19>:  movq   %rdi, %rcx
    0x100000ea6 <+22>:  movl   %esi, -0xc(%rbp)
    0x100000ea9 <+25>:  movl   %edx, -0x10(%rbp)
    0x100000eac <+28>:  movq   %rcx, -0x18(%rbp)
->  0x100000eb0 <+32>:  movq   %rdi, -0x20(%rbp)
    0x100000eb4 <+36>:  movq   %rax, %rdi
    0x100000eb7 <+39>:  movb   $0x0, %al
    0x100000eb9 <+41>:  callq  0x100000f16               ; symbol stub for: NSLog
    0x100000ebe <+46>:  leaq   0x19b(%rip), %rcx         ; @"I am a block!"
    0x100000ec5 <+53>:  movq   %rcx, %rdi
    0x100000ec8 <+56>:  movb   $0x0, %al
    0x100000eca <+58>:  callq  0x100000f16               ; symbol stub for: NSLog
    0x100000ecf <+63>:  leaq   0x1aa(%rip), %rcx         ; @"c = %d"
    0x100000ed6 <+70>:  movl   -0xc(%rbp), %esi
    0x100000ed9 <+73>:  movq   %rcx, %rdi
    0x100000edc <+76>:  movb   $0x0, %al
    0x100000ede <+78>:  callq  0x100000f16               ; symbol stub for: NSLog
    0x100000ee3 <+83>:  leaq   0x1b6(%rip), %rcx         ; @"b = %d"
    0x100000eea <+90>:  movl   -0x10(%rbp), %esi
    0x100000eed <+93>:  movq   %rcx, %rdi
    0x100000ef0 <+96>:  movb   $0x0, %al
    0x100000ef2 <+98>:  callq  0x100000f16               ; symbol stub for: NSLog
    0x100000ef7 <+103>: leaq   0x1c2(%rip), %rcx         ; @
    0x100000efe <+110>: movq   -0x20(%rbp), %rdi
    0x100000f02 <+114>: movl   0x20(%rdi), %esi
    0x100000f05 <+117>: movq   %rcx, %rdi
    0x100000f08 <+120>: movb   $0x0, %al
    0x100000f0a <+122>: callq  0x100000f16               ; symbol stub for: NSLog
    0x100000f0f <+127>: addq   $0x20, %rsp
    0x100000f13 <+131>: popq   %rbp
    0x100000f14 <+132>: retq   

这里不用纠结汇编具体写了什么,从最右边栏的信息,我们也能大致猜测出这段汇编码应该就是block中所包裹的那段函数体实现,事实也确实如此,这里我们只需要注意第一句汇编码最左侧的0x100000e90,这个就是block中所包含的函数实现的入口地址。而刚才我们的截图中block内部所包含的FuncPtr = (void *)0x100000e90,也确实验证这个FuncPtr所指向的就是block内部所包含的那段函数实现的入口。

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

推荐阅读更多精彩内容

  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 929评论 1 3
  • Block 前前后后看了4、5遍《Objective-C高级编程》的Block模块,对Block相关的内容有一定的...
    sycasl阅读 518评论 0 0
  • 摘要block是2010年WWDC苹果为Objective-C提供的一个新特性,它为我们开发提供了便利,比如GCD...
    西门吹雪123阅读 918评论 0 4
  • Block概要 Block:带有自动变量的匿名函数。 匿名函数:没有函数名的函数,一对{}包裹的内容是匿名函数的作...
    zweic阅读 505评论 0 2
  • 早晨的早勤也没有上,一觉睡到八点半,因为有电视电话会议回家又晚了,十一点了才到家,奶奶过了会给大烁烁把饭弄好了,我...
    易如人生阅读 136评论 0 0