iOS进阶之Block的本质及原理
前言
相信稍微有点开发经验的开发者,应该都对block有一定的了解。刚开始使用block的时候,可能觉得很方便。知道怎么去用,但是知其然知其所以然的态度来分析一下block的原理和内部结构是什么。
blcok是什么?
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block是封装函数及其上下文的OC对象
block内部构成(以局部变量为例子来了解整个执行流程)
一、通过main.m生成对应的main.cpp文件
- 1、创建一个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;
}
- 2、然后在终端进到main.m所在的文件目录下,指向下面的指令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
然后在main.m所在的文件夹下对象oc的main文件编译生成了对应的c++的main.cpp文件,在main.cpp中可以查看对应的。如下图:
二、查看block内部结构
1、编译后的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;
}
2、从以上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;
}
};
从上面的代码中可以看出:
声明了struct __block_impl impl结构体;
声明了struct __main_block_desc_0* Desc结构体;
声明int age;
__main_block_imp_0结构体内,声明了一个同名构造函数__main_block_imp_0,构造函数中对一些变量进行了赋值最终会返回一个结构体。最终将一个__main_block_imp_0结构体的地址赋值给了block变量
-
_main_block_impl_0结构体内可以发现__main_block_impl_0构造函数中传入了四个参数:
- (void *)__main_block_func_0
- &__main_block_desc_0_DATA
- age(age(_age)表示传入的_age参数会自动赋值给age成员,相当于age = _age)
- int flags=0(其中flags有默认值,也就说flags参数在调用的时候可以省略不传。)
一、先查看一下__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)};
如上代码可以表明:
- __block_impl结构体,即为Block的结构体,可理解为Block的类结构。
- _main表示的是main.cpp的文件名,_block表示定义Block的变量名字
- block也有一个isa指针,所以block是一个OC对象
- FuncPtr:指向调用函数的地址
- __main_block_desc_0 :block描述信息
- Block_size:block的大小
二、声明的int age
block是封装函数及其上下文的OC对象,block可以根据上下文环,因为block内部使用age,所以在结构体也声明int age,并且把赋值__main_bloc_impl_0里面的age参数值赋值10给age。
三、__main_block_imp_0中的(void *)__main_block_func_0、&__main_block_desc_0_DATA、age分别代表什么。
一、首页来查看(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函数代码表明:
- 首先取出block中age的值,紧接着可以看到四个熟悉的NSLog,可以发现这段代码恰恰是我们在block块中写下的代码.
- 表明__main_block_func_0函数中其实存储着我们block中写下的代码
4._cself相当于Objective-C中的self,前面不是__main_block_impl_0结构体函数把传进来的值传进来,int age = __cself->age; // bound by copy表明在函数内声明一个int age局部变量。所以在block后定义后改变值的话,block内部函数age值是不变的。 - 我们写在block块中的代码封装成__main_block_func_0函数,并将__main_block_func_0函数的地址传入了__main_block_impl_0的构造函数中保存在结构体内。
二、其次我在看看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中存储着两个参数:
- reserved赋值为0
- Block_size存储__main_block_impl_0的占用空间大小
- 将__main_block_desc_0结构体的地址传入__main_block_func_0中赋值给Desc。
三、最后还有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捕获的。
四、通过上面对__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()执行内部代码
//block(10,10)执行的内部代码
((void (*)(__block_impl *, int, int))
((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
通过查看block执行内部代码表明:
- 发现调用block是通过block找到FunPtr直接调用
- 通过上面分析我们知道block指向的是__main_block_impl_0类型结构体
- 我们发现__main_block_impl_0结构体中并不直接就可以找到FunPtr,而FunPtr是存储在__block_impl中的
为什么block可以直接调用__block_impl中的FunPtr呢?
- 通过上边的执行block(10,10)的内部代码,这块(__block_impl *)block代码表明block强制转化为__block_impl类型的
- 因为__block_impl是__main_block_impl_0结构体的第一个成员,相当于将__block_impl结构体的成员直接拿出来放在__main_block_impl_0中
- 那么也就说明__block_impl的内存地址就是__main_block_impl_0结构体的内存地址开头。所以可以转化成功。并找到FunPtr成员。
通过以上面的叙述也可以表明:
- FunPtr中存储着通过代码块封装的函数地址,那么调用此函数,也就是会执行代码块中的代码。
- 回头查看__main_block_func_0函数,可以发现第一个参数就是__main_block_impl_0类型的指针。也就是说将block传入__main_block_func_0函数中,便于从中取出block捕获的值。
四、验证block的本质是__main_block_impl_0结构体类型
通过模拟一下block的内部代码:
#import
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
五、验证一下block是不是对象
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)
从打印结果可以表明:
- 输出了block1的类型,也证实了block是对象,最终继承NSObject
- block的类型是NSGlobalBlock(下面会讲)
block的三种类型基本概念
根据Block对象创建时所处数据区不同而进行区别:
- _NSConcreteStackBlock:在栈上创建的Block对象
- _NSConcreteMallocBlock:在堆上创建的Block对象
- _NSConcreteGlobalBlock:全局数据区的Block对象
如下图:
对着三种类型block进行copy操作后的结果?
- __NSGlobalBlock __ 调用copy操作后,什么也不做
- __NSMallocBlock __ 调用copy操作后,复制效果是:引用计数增加;副本存储位置是堆
- __NSStackBlock __ 调用copy操作后,复制效果是:从栈复制到堆;副本存储位置是堆
一、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 };
通过声明了全局变量、全局静态变量、局部静态变量三种变量,通过上代码表明:
- 发现可以捕获的只有&staic_var并且以指针形式截获局部静态变量。
- 全局变量、全局静态变量直接用,所以在声明block后修改全局变量、全局静态变量是可以的
- __main_block_func_0在函数方法内,赋值指针表明,所以在声明block后,修改staic_var局部静态变量的值是可以的。
NSMallocBlock
在ARC情况下,编译器会根据情况自动将栈上的block复制到堆上,那么几种情况的Block的类型进行copy?
- 匿名函数返回赋值给block变量
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
大多数情况都是block作为函数返回值时进行copy操作。
怎么判定是block类型是NSMallocBlock?
需要两个条件:
- 对匿名的block进行copy(匿名函数block赋给block变量是一种情况)
- 捕获到成员变量或者是__block声明的局部成员变量至少一种
下面就通过代码来表示一下:
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的类型是NSMallocBlock类型
- 成员变量在block声明后去改31,执行block发现值还是30.
- __block在block声明后去改41,执行block发现值变成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;
};
结构里的结构可以看出:
- 保存int block_var变量
- 有一个指向__Block_byref_count_0实例的指针__forwarding
再看一下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));
}
发现在block执行函数里重新声明的int var把__cself(相当于__main_block_impl_0)里的var值赋值给它
发现在block执行函数里重新声明的__Block_byref_block_var_0 *block_var,并把__cself的block_var地址给了它
打印block_var的值发现变成了block_var->__forwarding->block_var说明了结构体内__forwarding指针指向还是自己。
为什么要通过__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
通过以上可以表明:
- __block声明的变量就变成__Block_byref_block_var_0结构体对象
- 在捕获前在栈上,捕获后相当于把__block 变量copy到堆上
- 捕获后,copy到堆的结构体和捕获前栈的结构体都包含__forwarding指针都指向堆结构体__block变量的地址
NSStackBlock
栈上的block随时会被销毁,受系统控制。怎么声明NSStackBlock类型?
- 匿名声明的block
- 捕获局部变量或者__block局部变量至少一种
- 没有进行copy操作
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__
总结
通过这边文章,可以总结一下:
- 全局变量、全局静态变量不被block捕获,局部变量、局部静态变量被block捕获
- 不被block捕获或者只捕获局部静态变量的block类型是NSGlobalBlock
- 局部变量在block内传的是值,局部静态变量传的指针,全局(静态)变量都是直接用。
- block是栈类型,对其进行copy操作,block就变成了NSMallocBlock
- __block声明的变量就变成__Block_byref_block_var_0结构体对象,在捕获前是放在存在栈上的。
- 栈上__block的__forwarding指向本身
- 栈上__block复制到堆上后,栈上block的__forwarding指向堆上的block,堆上block的__forwarding指向本身