内容简介:block在网络上的文章也比较多,本文将开发中block使用细节和block实现原理结合起来,
前言
block在网络上的文章也比较多,
本文将开发中block使用细节和block实现原理结合起来,
加上个人的理解,
帮助大家更好地理解block和使用block。
问题
block的意义
block为什么不能修改外部变量?这里的外部变量又指的是什么?
block为什么要用copy
被block引用的对象,引用计数为何+=2?
__block 又是什么原理?
循环引用究竟是为何引起的?
等等
block产生的意义
程序始终都要遵循逐行执行的原则,
而block 可以理解为 逻辑触发执行
定时器 可以理解为 时间触发执行
举个有点意思的例子:
背景:我现在身处异世界,我有很多酷炫的技能
我现在有这样一个技能,发动这个技能,我可以在当前这个地方留一个分身并安排好任务,然后我可以继续做我自己的事情了,等我再次使用这个技能时,我的分身将开始处理这项任务,处理完成后,我的分身将会消失。
隐含的问题:给分身安排具体任务的时候,这个任务在未来是否能够完成是未知的,因为我们不知道未来会发生什么
代码亦是如此。
进一步理解:程序是严密而又真实的,所以并不存在什么高科技呀,黑魔法呀~
block其实就是用程序实现了代码缓存和对象缓存,相应的对象缓存在block对象中,
执行block,就是把缓存的代码执行一遍,而相应的对象的状态,可能会因为执行完缓存下来的代码而发生变化。
到此,希望你对block会产生了那么一点点的兴趣~
依旧还是要从源码说起
强调:以下讲的变量a 默认指的是 自动变量auto,不是对象类型
#import
int main(int argc, char * argv[]) {
@autoreleasepool {
int a = 0;
void (^block)(void) = ^{
NSLog(@"%d",a);
};
block();
return 0;
}
}
将其翻译成底层c++文件, 一点一点看
main函数里的代码
int a = 10; void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
这是一大串什么?
不要着急,我们从上到下,从左往右进行说明。
首先,block初始化这一行
// 原代码 void (^block)(void) = ^{ NSLog(@"%d",a); }; // 翻译成c++代码 void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); // 简化后 block = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,a);
-
void (*block)(void) 函数指针,参数void,返回值void
-
((void (*)()) 上面函数指针的类型,作用是强转
-
&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)) 分解来看
分解第一步:__main_block_impl_0 组成
__main_block_impl_0是一个结构体,包含一个构造函数和三个变量
一个普通的结构体类型,一个结构体指针类型,
还有一个和外部的变量a,名称一样,类型一样。
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 的 构造函数
第一个参数void *fp,传入的是 &__main_block_func_0
__main_block_func_0是一个静态函数,
它的参数又是__main_block_impl_0这个结构体指针
即 FuncPtr 存储的是 __main_block_func_0 静态函数地址
impl.FuncPtr = fp; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_lb_tby1gwds2fnb89dzkf4cq3xh0000gn_T_main_9bc6d9_mi_0,a); }
是不是快要绕晕了呢?
在静态函数__main_block_func_0内,
先是获取__main_block_impl_0结构体内的变量a,然后打印出来。
从这一点可以看出 静态函数的作用就是写在block内部的代码的容器和入口。
而__main_block_impl_0结构体内的变量a的值来自外部,是在结构体的构造函数内进行了赋值操作。
得到如下结论:
获取到的基础变量的值在block初始化的时候已经确定了,
block外部的变量a在后续无论做什么操作,都不会影响block内部保存的变量a
这就是在block内部直接修改自动变量会报错的原因,
如果这里直接允许修改了,在目前条件下,也仅仅能做到block内部的变量a进行重新赋值操作,和外部变量a没有关系,产生歧义。
当然,使用__block修饰的自动变量可以进行修改,那么又是什么原理呢?我们先继续解读当前的源码
第二个参数 &__main_block_desc_0
__main_block_desc_0静态结构体存储的是block的基础信息
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)};
第三个参数为我们用到的外部的变量a,赋值给结构体内的变量a
第四个参数没有传,默认为0 (辅助变量,可以忽略,不会影响block的理解)
分解第三步:__main_block_impl_0 中的 __block_impl
存储的block的信息,相应的参数在构造函数内进行了赋值操作
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
最后 block初始化代码和执行代码放在一起看
void (^block)(void) = ^{ NSLog(@"%d",a); }; // c++代码 void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); // 简化 block = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a) block(); // c++代码 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); // 简化 block->FuncPtr(block);
block->FuncPtr 指的就是 __main_block_func_0这个函数的地址,参数为block本身
static void __main_block_func_0(struct __main_block_impl_0 *__cself) { int a = __cself->a; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_lb_tby1gwds2fnb89dzkf4cq3xh0000gn_T_main_19f603_mi_0,a); }
串起来再看block构造
block初始化:block 通过 __main_block_impl_0结构体构造函数进行初始化,同时生成__main_block_func_0静态函数,并将其地址以及其他相关信息储存在__block_impl这个结构体成员变量中。
其中,__block_impl这个结构体成员变量是__main_block_impl_0的首地址。
block调用:block指针指向的是__main_block_impl_0 的首地址,即__block_impl的地址,所以可以强转为(__block_impl *)类型,并访问其成员FuncPtr,指向的是静态函数地址,并传入参数__main_block_impl_0,也就是block自己。
名称 | 类型 | 是否随block内容改变 | 生成顺序 | 说明 |
---|---|---|---|---|
__block_impl | 结构体 | NO | 1 | 底层结构体,属于__main_block_impl_0成员 |
__main_block_impl_0 | 结构体 | YES | 2 | 缓存变量/对象,主结构体 |
__main_block_func_0 | 静态函数 | YES | 2 | 缓存代码,地址存放在__block_impl中 |
该缓存代码指的是:
总结:
将外部变量/对象的信息缓存在__main_block_impl_0中,
将代码缓存在静态函数中,
静态函数在缓存代码的时候需要用到外部变量/对象的信息
执行block就是执行了该静态函数
如果到此有不理解的地方可能c++基础较薄弱,百度一下辅助查看
最后
本文说明了block的用意,揭开了黑魔法的初级面纱,细讲了block基础源码,解释了基础类型的变量为何不能在block内部直接修改
后续会借此基础之上,继续解读目录中的问题
作者:tigerAndBull
链接:https://www.jianshu.com/p/1e8855a1b47d
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- LSM原理解读
- 超详细的webpack原理解读
- 动画+原理+代码,解读十大经典排序算法
- 收款神器!解读聚合收款码背后的原理
- Volcano 设计原理全面解读,一看就懂
- Knative 系列(一):基本概念和原理解读
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。