博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Block的本质
阅读量:5170 次
发布时间:2019-06-13

本文共 6506 字,大约阅读时间需要 21 分钟。

本质

先写一个简单的block

int main(int argc, const char * argv[]) {    @autoreleasepool {        int a = 10;       void (^block) (void) =   ^{           NSLog(@"this is block");           NSLog(@"this is block %d", a);        };        }    }复制代码

使用 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m 命令将其OC代码转化为底层的C++代码,观察block的底层结构。 打开main.cpp代码,直接搜索main(找到main函数,代码如下:

int main(int argc, const char * argv[]) {    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;          int a = 10;        void (*block) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));    }    return 0;}复制代码

可能看到这大家有点不太明白,那么一堆怎么看啊,咱们简化一下,把那些强制转换去掉

int main(int argc, const char * argv[]) {    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;          int a = 10;        *block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);    }    return 0;}复制代码

大家可以看到block就相当于是__main_block_impl_0,那么__main_block_impl_0是什么呢?在main.cpp代码里搜索

__main_block_impl_0

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int a;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};复制代码

很清晰,__main_block_impl_0是个结构体,那么相当于block就是一个结构体. 结构体中包含两个不同的结构体变量__block_impl 和 __main_block_desc_0,并且__main_block_impl_0结构体内有一个同名构造函数__main_block_impl_0,构造函数中对一些变量进行了赋值最终会返回一个结构体,那么也就是说最终将一个__main_block_imp_0结构体的地址赋值给了block变量.

__main_block_impl_0结构体内可以发现__main_block_impl_0构造函数中传入了四个参数.(void *)__main_block_func_0、&__main_block_desc_0_DATA、a、flags,其中flags有默认值,也就是说flags参数在调用的时候可以省略不传.而最后的a则表示传入的_a参数会自动赋值给a成员,相当于a=_a,那么咱们看看前两个参数分别代表什么?

由上面的简化代码:

*block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);复制代码

可以看出fp是__main_block_func_0,desc是__main_block_desc_0_DATA

__main_block_func_0

在__main_block_func_0函数中首先取出block中a的值,紧接着可以看到两个熟悉的NSLog,可以发现这两段代码恰恰是我们在block块中写下的代码。 那么__main_block_func_0函数中其实存储着我们block中写下的代码。而__main_block_impl_0函数中传入的是(void *)__main_block_func_0,也就说将我们写在block块中的代码封装成__main_block_func_0函数,并将__main_block_func_0函数的地址传入了__main_block_impl_0的构造函数中保存在结构体内。

&__main_block_desc_0_DATA

可以看到_main_block_desc_0存储着两个参数,reserved和Block_size,并且reserved赋值为0,而Block_size则存储着__main_block_impl_0的占用空间大小。最终将__main_block_desc_0结构体的地址传入__main_block_func_0中赋值给Desc。

__block_impl

struct __block_impl {  void *isa;  int Flags;  int Reserved;  void *FuncPtr;};复制代码

__block_impl包含isa指针,说明block本质上也是一个OC对象,咱们再看看结构体里的FuncPtr是什么?前面实际上可以看出FuncPtr指向block所封装的代码块地址,但咱们从另一方面也说明一下

首先按照它源码里的结构来写block底层,首先block相当于__main_block_impl_0,而__main_block_impl_0里面有两个结构体变量,如下代码

struct __block_imp {    void *isa;    int Flags;    int Reserved;    void *FuncPtr;};struct __main_block_desc_0 {    size_t reserved;    size_t Block_size;};struct __main_block_imp_0 {    struct __block_imp impl;    struct __main_block_desc_0* Desc;    int a;};复制代码

重新编写一下main.m文件

int main(int argc, const char * argv[]) {    @autoreleasepool {         int a = 10;        void (^block) (void) =   ^{            NSLog(@"this is block");            NSLog(@"this is block %d", a);        };      //因为上面已经说了block相当于__main_block_imp_0,那么这里我给它赋值      struct __main_block_imp_0 *blockStruct = (__bridge struct __main_block_imp_0 *)block;        NSLog(@"====");      block();    }    return 0;}复制代码

打断点调试一下

可以看到__block_imp的FuncPtr内存地址是0x100000ca0,接下来执行block里面的代码

Debug->Debug Workflow->Always Show Disassembly

可以看出block块里面的代码开始的内存地址也是0x100000ca0,由此可以知道__block_imp的FuncPtr指向block所封装的代码块地址,等执行block时会通过FuncPtr寻找将要执行的代码块,并且调用

当然上面的示例从侧面也证明block的本质就是__main_block_impl_0结构体类型

总结一下__main_block_impl_0结构体构造函数:

  1. _block_impl结构体中isa指针存储着&_NSConcreteStackBlock地址,可以暂时理解为其类对象地址,block就是_NSConcreteStackBlock类型的。
  2. block代码块中的代码被封装成__main_block_func_0函数,FuncPtr则存储着__main_block_func_0函数的地址。
  3. Desc指向__main_block_desc_0结构体对象,其中存储__main_block_impl_0结构体所占用的内存。

总结

下面用一张图来展示block底层结构体的关系

block底层的数据结构:

block的变量捕获

为了保证block内部能够正常访问外部的变量,block有一个变量捕获机制。

局部(auto)变量

回顾最开始的代码

int main(int argc, const char * argv[]) {    @autoreleasepool {        int a = 10;       void (^block) (void) =   ^{           NSLog(@"this is block");           NSLog(@"this is block %d", a);        };        }    }复制代码

在block内部引用外部变量,它是一个局部变量,看看其转化后的代码

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int a;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};复制代码

结构体内部有一个int类型的变量a,用于存储所引用的外部变量的值.因为是值存储,所以在block生成之后,无论外部变量做何修改,a依然是之前所定义的值

static变量

咱们把示例的代码改造一下,变量前面加个static关键字

int main(int argc, const char * argv[]) {    @autoreleasepool {        static int a = 10;        void (^block) (void) =   ^{            NSLog(@"this is block %d", a);        };        block();    }    return 0;}复制代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m转成c++代码

struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int *a;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};复制代码

此时,大家可以看到,使用static修饰的变量在block内部为指针传递,block直接捕获外部变量的内部地址,此时若外部变量在block声明之后修改,再block内部使用也会同步修改,因为其是从同一内部地址取的值

这儿大家想一个问题,为什么两种变量会有差异呢?

因为自动变量可能会被销毁,block在执行的时候有可能自动变量已经被销毁了,那么此时如果再去访问被销毁的地址肯定会发生坏内存访问,因此对于自动变量一定是值传递而不可能是指针传递。而静态变量不会被销毁,所以完全可以传递地址。而因为传递的是地址,所以在block调用之前修改地址中保存的值,在block中使用值会随之改变

全局变量

通过上面的讲解,大家肯定有一定的基础了,咱们就来个全的

int age = 60;int main(int argc, const char * argv[]) {    @autoreleasepool {        int a  = 10;        static int b = 20;        void (^block) (void) =   ^{            NSLog(@"age is %d a is %d b is %d", age, a, b);        };        block();    }    return 0;}复制代码

上面的代码包含了全局变量age,局部变量a,static变量b,咱们来看其底层是什么样的

int age = 60;struct __main_block_impl_0 {  struct __block_impl impl;  struct __main_block_desc_0* Desc;  int a;  int *b;  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {    impl.isa = &_NSConcreteStackBlock;    impl.Flags = flags;    impl.FuncPtr = fp;    Desc = desc;  }};复制代码

通过上述代码可以发现,__main_block_imp_0并没有添加age变量,因此block不需要捕获全局变量,因为全局变量无论在哪里都可以访问。

总结一句话:局部变量因为跨函数访问所以需要捕获,全局变量在哪里都可以访问 ,所以不用捕获。

局部变量都会被block捕获,自动变量是值捕获,静态变量为地址捕获。全局变量则不会被block捕获

转载于:https://juejin.im/post/5d633734f265da03bb4ab4d0

你可能感兴趣的文章
SQLServer中的CTE通用表表达式
查看>>
C# 3.0 LINQ的准备工作
查看>>
静态代码审查工具FxCop插件开发(c#)
查看>>
创建代码仓库
查看>>
理解裸机部署过程ironic
查看>>
Django 组件-ModelForm
查看>>
zabbix 二 zabbix agent 客户端
查看>>
大数据分析中,有哪些常见的大数据分析模型?
查看>>
如何防止Arp攻击
查看>>
ClassList 标签的用法
查看>>
小细节:Java中split()中的特殊分隔符 小数点
查看>>
【编程思想】【设计模式】【行为模式Behavioral】中介者模式Mediator
查看>>
后端接口时间戳或者随机数的作用
查看>>
tomcat docBase 和 path
查看>>
java默认语法、EL、JSTL表达式,JSTL和struts Tag标签的使用总结
查看>>
Vue笔记:使用 axios 发送请求
查看>>
富文本编辑器 - RichEditor
查看>>
java webcontroller访问时报415错误
查看>>
qcow2、raw、vmdk等镜像格式
查看>>
Jzoj5455【NOIP2017提高A组冲刺11.6】拆网线
查看>>