前言
block在网络上的文章也比较多,
本文将开发中block使用细节和block实现原理结合起来,
加上个人的理解,
帮助大家更好地理解block和使用block。
问题
- block的意义
- block为什么不能修改外部变量?这里的外部变量又指的是什么?
- block为什么要用copy
- 被block引用的对象,引用计数为何+=2?
- __block 又是什么原理?
- 循环引用究竟是为何引起的?
等等
block产生的意义
程序始终都要遵循逐行执行的原则,
而block 可以理解为 逻辑触发执行
定时器 可以理解为 时间触发执行
举个有点意思的例子:
背景:我现在身处异世界,我有很多酷炫的技能
我现在有这样一个技能,发动这个技能,我可以在当前这个地方留一个分身并安排好任务,然后我可以继续做我自己的事情了,等我再次使用这个技能时,我的分身将开始处理这项任务,处理完成后,我的分身将会消失。
隐含的问题:给分身安排具体任务的时候,这个任务在未来是否能够完成是未知的,因为我们不知道未来会发生什么,
代码亦是如此。
进一步理解:程序是严密而又真实的,所以并不存在什么高科技呀,黑魔法呀~
block其实就是用程序实现了代码缓存和对象缓存,相应的对象缓存在block对象中,
执行block,就是把缓存的代码执行一遍,而相应的对象的状态,可能会因为执行完缓存下来的代码而发生变化。
到此,希望你对block会产生了那么一点点的兴趣~
依旧还是要从源码说起
强调:以下讲的变量
a 默认指的是 基础类型的变量,不是对象类型
#import <UIKit/UIKit.h>
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中 |
该缓存代码指的是:
缓存代码一词,代码的意思.png总结:
将外部变量/对象的信息缓存在__main_block_impl_0中,
将代码缓存在静态函数中,
静态函数在缓存代码的时候需要用到外部变量/对象的信息
执行block就是执行了该静态函数
如果到此有不理解的地方可能c++基础较薄弱,百度一下辅助查看
最后
本文说明了block的用意,揭开了黑魔法的初级面纱,细讲了block基础源码,解释了基础类型的变量为何不能在block内部直接修改
后续会借此基础之上,继续解读目录中的问题
网友评论