目录
3.1 创建 Command Line Tool 项目
3.2 获取 main 文件
4.1 查看内部结构
4.2 声明的 int age
4.3 参数分析
8.1 NSGlobalBlock
8.2 NSMallocBlock
8.3 NSStackBlock
9. 总结
相信稍微有点开发经验的开发者,应该都对 block 有一定的了解。刚开始使用 block 的时候,可能觉得很方便。知道怎么去用,但是知其然知其所以然的态度来分析一下 block 的原理和内部结构是什么。
3.1 创建 Command Line Tool 项目
创建一个 macOS 的 Command Line Tool 项目,在项目里写上 block 代码如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void(^block)(int, int) = ^(int a, int b) {
NSLog(@"this is a block! -- %d", age);
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
};
age = 20;
block(10,10);
}
return 0;
}
3.2 获取 main 文件
然后在终端进到 main.m 所在的文件目录下,指向下面的指令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
然后在 main.m 所在的文件夹下对象 oc 的 main 文件编译生成了对应的 c++ 的 main.cpp 文件,在 main.cpp 中可以查看对应的。如下图:
编译后的 main 方法内代码如下:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
void(*block)(int, int) = ((void (*)(int, int))
&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
//block(10,10)的的内部结构代码
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
}
return 0;
}
从以上 c++ 代码中 block 的声明和定义分别与 oc 代码中相对应显示。将 c++ 中 block 的声明和调用分别取出来查看其内部实现。定义 block 变量对应的代码:
void(*block)(int, int) = ((void (*)(int, int))
&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
//简化后的代码
void (*block)(int, int) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, age);
上述定义代码中,可以发现所有带 _main_block ,__main 表示是 main.cpp 文件,_block 代表 block 的变量名字就是 block。 block 定义中调用了 __main_block_impl_0 函数,并且将 __main_block_impl_0 函数的地址赋值给了 block。
所以我先来看看 __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 age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
//在函数栈上声明,则为_NSConcreteStackBlock
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
从上面的代码中可以看出:
4.1 查看内部结构
先查看一下__block_impl 和 __main_block_desc_0 的内部结构,代码如下
struct __block_impl { void *isa; //指明对象的 Class int Flags; int Reserved; void *FuncPtr;};//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)};
如上代码可以表明:
4.2 声明的 int age
block 是封装函数及其上下文的 OC 对象,block 可以根据上下文环,因为block 内部使用 age,所以在结构体也声明 int age,并且把赋值__main_bloc_impl_0 里面的 age 参数值赋值 10 给 age。
4.3 参数分析
__main_block_imp_0 中的 (void *)__main_block_func_0、&__main_block_desc_0_DATA、age 分别代表什么
4.3.1 首页来查看(void *)__main_block_func_0 的内部结构
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_f3f131_mi_0, age);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_f3f131_mi_1);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_f3f131_mi_2);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_f3f131_mi_3);
}
从上边 __main_block_func_0 函数代码表明:
4.3.2 其次我在看看 struct __main_block_desc_0 *Desc 内部结构,&__main_block_desc_0_DATA 赋值给了 Desc
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)
};
从上面的代码中可以表明,__main_block_desc_0 中存储着两个参数:
4.3.3 age 参数
age 也就是我们定义的局部变量
因为在 block 块中使用到 age 局部变量,所以在 block 声明的时候这里才会将 age 作为参数传入,也就说block会捕获 age
如果没有在 block 中使用 age,这里将只会传入 (void *)__main_block_func_0,&__main_block_desc_0_DATA 两个参数。
这里可以根据源码思考一下为什么当我们在定义 block 之后修改局部变量age 的值,在 block 调用的时候无法生效?
block 在定义的之后已经将 age 的值传入,存储在 __main_block_imp_0 结构体中并在调用的时候将 age 从 block 中取出来使用。
在 block 定义之后对局部变量进行改变是无法被 block 捕获的。
4.3.4 通过上面对 __main_block_impl_0 结构体构造函数三个参数在把上面的代码写下强调一下
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
_block_impl 结构体中isa指针存储着 &_NSConcreteStackBlock 地址,可以暂时理解为其类对象地址,block 就是 _NSConcreteStackBlock 类型的。
block 代码块中的代码被封装成 __main_block_func_0 函数,FuncPtr 则存储着 __main_block_func_0 函数的地址。
Desc 指向 __main_block_desc_0 结构体对象,其中存储 __main_block_impl_0 结构体所占用的内存。
//block(10,10)执行的内部代码
((void (*)(__block_impl *, int, int))
((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
通过查看 block 执行内部代码表明:
为什么 block 可以直接调用 __block_impl 中的 FunPtr 呢?
通过以上面的叙述也可以表明:
#import <Foundation/Foundation.h>
struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
};
// 将底层的结构体强制转化为我们自己写的结构体,通过我们自定义的结构体探寻block底层结构体
struct __main_block_impl_0 *blockStruct = (__bridge struct __main_block_impl_0 *)block;
block(10, 10);
}
return 0;
}
在 block(10, 10) 和 NSLog(@"this is a block! -- %d", age) 打个断点,查看一下 blockStruct 内部的堆栈信息。如下图:
表明 FunPtr 函数地址是 0x100000eb0
在看一下 block{} 里实现的代码看一下地址和 FunPtr 地址是都一样,在 NSLog(@"this is a block! -- %d", age); 断点处时候,在 xcode->debug->Debug Workflow->always show Disassembly, 会显示堆栈信息中的函数调用地址。
如上图说明两个地址是一样的,可以说明 block 的本质就是_main_block_impl_0
void (^block1)(int, int) = ^(int a , int b){
NSLog(@"this is a block! -- %d", age);
NSLog(@"this is a block!");
NSLog(@"this is a block!");
NSLog(@"this is a block!");
};
NSLog(@"%@",[block1 class]);
NSLog(@"%@",[[block1 class] superclass]);
NSLog(@"%@",[[[block1 class] superclass] superclass]);
NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);
NSLog(@"%@",[[[[[block1 class] superclass] superclass] superclass] superclass]);
打印结果如下:
__NSGlobalBlock__
__NSGlobalBlock
NSBlock
NSObject
(null)
从打印结果可以表明:
根据 Block 对象创建时所处数据区不同而进行区别:
如下图:
对着三种类型 block 进行 copy 操作后的结果?
8.1 NSGlobalBlock
就是全局数据区的 Block 对象,那么什么时候 block 的类型是_NSConcreteGlobalBlock 类型?
主要还是看 block 捕获变量的类型来确定的,以下几种情况 block 类型是_NSConcreteGlobalBlock:
下面就通过代码来验证一下:
没有捕获变量的 block
void (^myBlock)(int, int) = ^(int a , int b){
NSLog(@"没有捕获变量");
};
NSLog(@"myBlockType:%@",[myBlock class]);
打印结果如下:
myBlockType:__NSGlobalBlock__
表明没有捕获变量的情况是 NSGlobalBlock 全局数据区 block
int global_var = 50; //全局变量
static int global_staic_var = 20; //全局静态变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int staic_var = 10; //局部静态变量
void (^myBlock)(int, int) = ^(int a , int b){
//只捕获局部静态变量
NSLog(@"staic_var:%d",staic_var);
};
NSLog(@"myBlockType:%@",[myBlock class]);
myBlock(10, 10);
}
}
通过运行打印结果如下:
myBlockType:__NSGlobalBlock__
staic_var:10
下面看一下捕获全局变量和局部变量编译成的 cpp 文件代码:
int global_var = 50; //全局变量
static int global_staic_var = 20; //全局静态变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int staic_var = 10; //局部静态变量
void (^myBlock)(int, int) = ^(int a , int b){
//只捕获局部静态变量
NSLog(@"staic_var:%d",staic_var);
NSLog(@"global_var:%d", global_var);
NSLog(@"global_staic_var:%d", global_staic_var);
};
NSLog(@"myBlockType:%@",[myBlock class]);
myBlock(10, 10);
}
}
通过指令转换成 cpp 文件看一下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
打开 main.cpp 文件:
int global_var = 50;
static int global_staic_var = 20;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *staic_var;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staic_var, int flags=0) : staic_var(_staic_var) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//发现全局静态变量、全局变量直接用
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
//赋值指针
int *staic_var = __cself->staic_var; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_5050a8_mi_0,global_var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_5050a8_mi_1,global_staic_var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_5050a8_mi_2,(*staic_var));
}
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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
static int staic_var = 10;
void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staic_var));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_5050a8_mi_3,((Class (*)(id, SEL))(void *)objc_msgSend)((id)myBlock, sel_registerName("class")));
((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 10);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
通过声明了全局变量、全局静态变量、局部静态变量三种变量,通过上代码表明:
8.2 NSMallocBlock
在 ARC 情况下,编译器会根据情况自动将栈上的 block 复制到堆上,那么几种情况的 Block 的类型进行 copy?
大多数情况都是 block 作为函数返回值时进行 copy 操作。 怎么判定是 block 类型是 NSMallocBlock? 需要两个条件:
下面就通过代码来表示一下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int var = 30; //局部变量
__block int block_var = 40; //加block局部变量
/*
捕获局部变量、block局部变量至少一种
匿名block赋给myBlock
*/
void (^myBlock)(int, int) = ^(int a , int b) {
NSLog(@"var:%d",var);
NSLog(@"block_var:%d",block_var);
};
var = 31;
block_var = 41;
NSLog(@"blockType:%@",[myBlock class]);
return 0;
}
return 0;
}
打印结果如下:
blockType:__NSMallocBlock__
var:30
block_var:41
打印结果表明:
那么为什么声明成员变量后改变不了,__block 声明的成员变量却可以?
下面我们通过底层转换成 c++ 代码来看一下为啥,因为上面已经写了 block底层的整体流程:
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int var = 30;
__attribute__((__blocks__(byref))) __Block_byref_block_var_0 block_var =
{(void*)0,(__Block_byref_block_var_0 *)&block_var, 0,
sizeof(__Block_byref_block_var_0), 40};
void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)
__main_block_func_0, &__main_block_desc_0_DATA, var,
(__Block_byref_block_var_0 *)&block_var, 570425344));
(block_var.__forwarding->block_var)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_b19769_mi_5,
((Class (*)(id, SEL))(void *)objc_msgSend)((id)myBlock, sel_registerName("class")));
((void (*)(__block_impl *, int, int))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock, 10, 10);
}
return 0;
}
我们声明的成员变量没发生变化,声明 __block 变量变成了
__attribute__((__blocks__(byref))) __Block_byref_block_var_0 block_var =
{(void*)0,(__Block_byref_block_var_0 *)&block_var, 0,
sizeof(__Block_byref_block_var_0), 40};
int 类型变成 __Block_byref_block_var_0 类型。__Block_byref_block_var_0 是啥?下面慢慢分析,看一下 void (^myBlock)(int, int) = ^(int a , int b){} 底层c++ 代码:
void (*myBlock)(int, int) = ((void (*)(int, int))&__main_block_impl_0((void *)
__main_block_func_0, &__main_block_desc_0_DATA, var,
(__Block_byref_block_var_0 *)&block_var, 570425344));
发现捕获的成员变量的值,捕获的 __block 变量类型变成了 &block_var 指针。 最大的变化就是 block_var 变量不再是 int 类型了,block_var 变成了一个指向 __Block_byref_block_var_0 结构体的指针,__Block_byref_block_var_0 结构如下:
struct __Block_byref_block_var_0 {
void *__isa;
__Block_byref_block_var_0 *__forwarding;
int __flags;
int __size;
int block_var;
};
结构里的结构可以看出:
再看一下 block 执行函数:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int var;
__Block_byref_block_var_0 *block_var; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staic_var, int _var, __Block_byref_block_var_0 *_block_var, int flags=0) : staic_var(_staic_var), var(_var), block_var(_block_var->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
__Block_byref_block_var_0 *block_var = __cself->block_var; // bound by ref
int var = __cself->var; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_b19769_mi_3,
var);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_gh_3fkjx11j0zb3jm_f752skdn00000gn_T_main_b19769_mi_4,
(block_var->__forwarding->block_var));
}
为什么要通过 __forwarding 指针完成对 count 变量的读写修改?
这样就保证无论是在栈上还是在堆上,都能通过都 __forwarding 指针找到在堆上创建的 block_var 这个 __main_block_func_0 结构体,通过结构内的 __forwarding 指针以完成对 block_var(第一个 block_var 是 __Block_byref_block_var_0 对象,第二个 block_var 是 int 类型变量)的访问和修改。
通过下面的图可以表明:
下面通过代码来验证一下:
__block int block_var = 10;
NSLog(@"block捕获前地址:%p",&block_var);
void (^myblock)(void) = ^{
NSLog(@"block_var:%d",block_var);
};
NSLog(@"block捕获后地址:%p",&block_var);
block_var = 20;
myblock();
打印的结果表明捕获前后的地址是变化的并且值改变了。
block捕获前地址:0x7ffeefbff4e8
block捕获后地址:0x102968608
block_var:20
通过以上可以表明:
8.3 NSStackBlock
栈上的 block 随时会被销毁,受系统控制。怎么声明 NSStackBlock 类型?
int var = 30; //局部变量
__block int block_var = 40; //加block局部变量
//没有捕获局部变量,所以是NSGlobalBlock类型
NSLog(@"Global Block:%@", [^{NSLog(@"Global Block");} class]);
//因为对其copy,说以是NSMallocBlock类型
NSLog(@"var Copy Block:%@", [[^{NSLog(@"Copy Block:%d",var);} copy] class]);
//虽然调用了局部变量,但是对其copy所以依然还是NSMallocBlock类型
NSLog(@"block_var Copy Block:%@", [[^{NSLog(@"Copy Block:%d",block_var);} copy] class]);
//没有copy,并且只捕获局部变量,所以N是SStackBlock
NSLog(@"var Stack Block:%@", [^{NSLog(@"Stack Block:%d",var);} class]);
//没有copy,并且只捕获局部变量,所以N是SStackBlock
NSLog(@"block_var Stack Block:%@", [^{NSLog(@"Copy Block:%d",block_var);} class]);
打印结果:
Global Block:__NSGlobalBlock__
var Copy Block:__NSMallocBlock__
block_var Copy Block:__NSMallocBlock__
var Stack Block:__NSStackBlock__
block_var Stack Block:__NSStackBlock__
9. 总结
通过这篇文章,作出如下总结: