刨根问底block
[toc]
1.block本质
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
1.1 block数据结构
在oc中的实现
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
验证
补充
- 将Objective-C代码转换为C\C++代码
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
- 如果需要链接其他框架,使用-framework参数。比如-framework UIKit
原理的oc代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
int temp = 10;
void(^block)(int,int) = ^(int a ,int b){
NSLog(@"这是一个block,---%d",temp);
};
temp = 20;
block(10,10);
}
return 0;
}
执行下面脚本,将oc代码转换成C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
如果遇到__weak运行时内容,则执行
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
转换为main.cpp后对应关系如下
int temp = 10;
void(^block)(int,int) = ^(int a ,int b){
NSLog(@"这是一个block,---%d",temp);
};
temp = 20;
block(10,10);
分别对应
转化后的c++代码如下:我们简称代码A(下面用到)
int temp = 10;
//定义block变量
void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, temp));
//执行bloc代码
temp = 20;
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
而__main_block_impl_0
的定义为
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int temp;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _temp, int flags=0) : temp(_temp) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
结构体'__block_impl'定义又为
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
所以__main_block_impl_0
可以为
struct __main_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
struct __main_block_desc_0* Desc;
int temp;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _temp, int flags=0) : temp(_temp) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
看到这个结构跟Block_layout
类似,另外temp
作为一个block变量
1.2 理解block数据结构以及调用
代码A里面都是一些强制转换,我们去掉强制转换,结构为
// 定义block变量
void(*block)(int,int) = &__main_block_impl_0(__main_block_func_0,
&__main_block_desc_0_DATA, temp);
// 执行block内部的代码
block->FuncPtr(block,10,10);
-
block
为__main_block_impl_0
函数执行结果,并把地址赋给
block
-
__main_block_impl_0
为__main_block_impl_0
结构体的构造函数
所以block指针是一个指向结构体对象,也就是block是结构体对象
1、__main_block_func_0
的代码为
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int temp = __cself->temp; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_c0a19f_mi_0,temp);
}
通过代码可以知道,把block内部执行逻辑赋给了__block_impl
的FuncPtr
2、__main_block_desc_0_DATA
为
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)};
通过sizeof(struct __main_block_impl_0)
可知道这个参数主要就是对block大小的描述
总结:
- 定义block时,将block内部执行函数封装到
__main_block_func_0
- 调用
__main_block_impl_0
结构体的构造函数,并把地址赋给block,__main_block_impl_0
有两个主要成员impl
和Desc
__main_block_func_0
作为第一个参数传入__main_block_impl_0
,__main_block_impl_0
的size等信息作为第二个参数__main_block_func_0
赋给了impl
的FuncPtr
- 调用block时,就是
FuncPtr
,并把block
作为参数传进去
2.变量捕获
变量类型:
- 自动变量:离开作用域就销毁的局部变量,就是auto变量
- static变量:局部变量内部使用static变量的变量,为static变量
2.1 auto变量捕获
代码A的局部变量 temp就是一个auto变量,因为C语言会默认给这种变量增加一个auto关键字,auto是默认关键字,
自动变量:离开作用域就销毁的变量,就是auto变量
代码A的打印结果为:
2020-10-25 08:55:59.897766+0800 Blocktest[7366:84510] 这是一个block,---10
Program ended with exit code: 0
结构是10 而不是20
原因:
如下代码,在定义block的时候,已经把temp作为参数传递给了结构体,结构体__main_block_impl_0里有一个变量temp,存储了外部的temp,也就是捕获了变量temp
void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, temp));
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int temp;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _temp, int flags=0) : temp(_temp) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
执行函数体的地方,打印的是__main_block_impl_0
内部的temp变量,所以没变
//定义block内部执行逻辑
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int temp = __cself->temp; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_c0a19f_mi_0,temp);
}
实际上这也就是auto变量的捕获,可以发现此时的变量是值传递的方式
2.2 static变量
修改代码为
int temp = 10;
static int v = 10;
void(^block)(int,int) = ^(int a ,int b){
NSLog(@"这是一个block,---%d",temp);
NSLog(@"这是一个block,---%d",temp1);
};
temp = 20;
temp1 = 20;
block(10,10);
打印结果是
2020-10-22 21:11:19.398797+0800 Blocktest[1739:21133] 这是一个block,---10
2020-10-22 21:11:19.399428+0800 Blocktest[1739:21133] 这是一个block,---20
Program ended with exit code: 0
clang查看转换后的c++源码为
//__main_block_impl_0部分
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int temp;
int *temp1;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _temp, int *_temp1, int flags=0) : temp(_temp), temp1(_temp1) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//__main_block_func_0 部分
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int temp = __cself->temp; // bound by copy
int *temp1 = __cself->temp1; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_e1517b_mi_0,temp);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_e1517b_mi_1,(*temp1));
}
//main部分
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int temp = 10;
static int temp1 = 10;
void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, temp, &temp1));
temp = 20;
temp1 = 20;
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
}
return 0;
}
发现可以知道静态变量,将temp1的地址值赋值给__main_block_impl_0
的temp1
指针,所以temp1修改为20时,打印的时候打的是temp1指针指向的地址数据,所以打印20
结论:static是指针传递捕获变量
延伸:为什么auto是值传递,static是指针传递
猜测:
- auto变量:在一个代码块里执行结束,会被销毁,所以在block里捕获没必要指针,因为被销毁,所以再次访问会造成野指针
- static变量:一直保存在内存中,代码块结束后,静态变量指针还在
2.3 全局变量
修改代码为
int temp_ = 10;
static int temp1_ = 10;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int temp = 10;
static int temp1 = 10;
void(^block)(int,int) = ^(int a ,int b){
NSLog(@"这是一个block,---%d",temp);
NSLog(@"这是一个block,---%d",temp1);
NSLog(@"这是一个block,---%d",temp_);
NSLog(@"这是一个block,---%d",temp1_);
};
temp = 20;
temp1 = 20;
temp_ = 20;
temp1_ = 20;
block(10,10);
}
return 0;
}
执行结果是
2020-11-02 21:27:48.500471+0800 Blocktest[2911:37937] 这是一个block,---10
2020-11-02 21:27:48.501084+0800 Blocktest[2911:37937] 这是一个block,---20
2020-11-02 21:27:48.501163+0800 Blocktest[2911:37937] 这是一个block,---20
2020-11-02 21:27:48.501201+0800 Blocktest[2911:37937] 这是一个block,---20
Program ended with exit code: 0
clang查看c++代码
int temp_ = 10;
static int temp1_ = 10;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int temp;
int *temp1;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _temp, int *_temp1, int flags=0) : temp(_temp), temp1(_temp1) {
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 temp = __cself->temp; // bound by copy
int *temp1 = __cself->temp1; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b62fb6_mi_0,temp);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b62fb6_mi_1,(*temp1));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b62fb6_mi_2,temp_);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b62fb6_mi_3,temp1_);
}
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;
int temp = 10;
static int temp1 = 10;
void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, temp, &temp1));
temp = 20;
temp1 = 20;
temp_ = 20;
temp1_ = 20;
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
}
return 0;
}
可以看到全局变量是直接访问,并没有捕获到block里面,所以修改后,值都变成是20
局部变量需要捕获,全局变量不需要捕获原因:
- 因为全局变量一直在内存中,可以一直访问,所以没必要捕获
- 局部需要捕获原因:还是代码块作用域的原因,因为在代码块作用域后,不能再次访问作用域里的局部变量。而block执行相当于在另一个作用域函数执行访问变量,所以需要捕获到block里面
2.4 对象捕获
创建student类,代码为:
@implementation Student
- (void)test{
void(^bloc)(void) = ^{
NSLog(@"stutend ====%@",self);
NSLog(@"stutend ====%@",self->_name);
};
bloc();
}
- (instancetype)initWithName:(NSString *)name
{
if (self = [super init]) {
self.name = name;
}
return self;
}
@end
clang查看c++代码为
struct __Student__test_block_impl_0 {
struct __block_impl impl;
struct __Student__test_block_desc_0* Desc;
Student *self;
__Student__test_block_impl_0(void *fp, struct __Student__test_block_desc_0 *desc, Student *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __Student__test_block_func_0(struct __Student__test_block_impl_0 *__cself) {
Student *self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_Student_05c7f3_mi_0,self);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_Student_05c7f3_mi_1,(*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Student$_name)));
}
static void __Student__test_block_copy_0(struct __Student__test_block_impl_0*dst, struct __Student__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __Student__test_block_dispose_0(struct __Student__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __Student__test_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __Student__test_block_impl_0*, struct __Student__test_block_impl_0*);
void (*dispose)(struct __Student__test_block_impl_0*);
} __Student__test_block_desc_0_DATA = { 0, sizeof(struct __Student__test_block_impl_0), __Student__test_block_copy_0, __Student__test_block_dispose_0};
static void _I_Student_test(Student * self, SEL _cmd) {
void(*bloc)(void) = ((void (*)())&__Student__test_block_impl_0((void *)__Student__test_block_func_0, &__Student__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)bloc)->FuncPtr)((__block_impl *)bloc);
}
static instancetype _Nonnull _I_Student_initWithName_(Student * self, SEL _cmd, NSString * _Nonnull name) {
if (self = ((Student *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Student"))}, sel_registerName("init"))) {
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)self, sel_registerName("setName:"), (NSString * _Nonnull)name);
}
return self;
}
static NSString * _Nonnull _I_Student_name(Student * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Student$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_Student_setName_(Student * self, SEL _cmd, NSString * _Nonnull name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Student, _name), (id)name, 0, 1); }
可以捕获到
因为oc函数转换成C++时,都会默认添加两个参数
(Student * self, SEL _cmd)
,参数分别为方法调用者,方法名。参数也是局部变量。
如何捕获
把self指针传递捕获到
__Student__test_block_impl_0
的Student *self
指针
2.5 总结
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
捕获机制如下
变量类型 | 捕获到block内部 | 访问方式 |
---|---|---|
auto | 捕获 | 值传递 |
static | 捕获 | 指针传递 |
全局变量 | 不捕获 | 直接访问 |
局部变量需要捕获,全局变量不需要捕获原因:
- 因为全局变量一直在内存中,可以一直访问,所以没必要捕获
- 局部需要捕获原因:还是代码块作用域的原因,因为在代码块作用域后,不能再次访问作用域里的局部变量。而block执行相当于在另一个作用域函数执行访问变量,所以需要捕获到block里面
为什么auto是值传递,static是指针传递
- auto变量:在一个代码块里执行结束,会被销毁,所以在block里捕获没必要指针,因为被销毁,所以再次访问会造成野指针
- static变量:一直保存在内存中,代码块结束后,静态变量指针还在
3.block类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
-
__NSGlobalBlock__
( _NSConcreteGlobalBlock ) -
__NSStackBlock__
( _NSConcreteStackBlock ) -
__NSMallocBlock__
( _NSConcreteMallocBlock )
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(int,int) = ^(int a ,int b){
};
NSLog(@"block类型==%@",[block class]);
NSLog(@"block父类类型==%@",[[block class] superclass]);
NSLog(@"block父类的父类类型==%@",[[[block class] superclass] superclass]);
NSLog(@"block父类的父类的类型==%@",[[[[block class] superclass] superclass] superclass]);
}
return 0;
}
执行结果
2020-11-02 22:12:16.481399+0800 Blocktest[6752:90620] block类型==__NSGlobalBlock__
2020-11-02 22:12:16.481856+0800 Blocktest[6752:90620] block父类类型==__NSGlobalBlock
2020-11-02 22:12:16.481939+0800 Blocktest[6752:90620] block父类的父类类型==NSBlock
2020-11-02 22:12:16.481992+0800 Blocktest[6752:90620] block父类的父类的类型==NSObject
Program ended with exit code: 0
进一步验证block是oc对象
3.1执行三种类型
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block1)(void) = ^(){
NSLog(@"block1");
};
int age = 10;
void(^block2)(void) = ^(){
NSLog(@"block2===%d",age);
};
NSLog(@"%@ %@ %@",[block1 class] ,[block2 class],[^{
NSLog(@"%d",age);
}class]);
}
return 0;
}
执行结果
2020-11-02 22:18:15.039022+0800 Blocktest[7396:97958] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
Program ended with exit code: 0
- 但是clang查看c++源码如下,发现都是_NSConcreteStackBlock类型。
- 这是因为oc是动态语言,一切以runtime运行时的结果为准
- 编译完是下c++代码,但是运行时通过runtime运行时动态修改,所以不一样。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_8d9d27_mi_0);
}
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)};
struct __main_block_impl_1 {
struct __block_impl impl;
struct __main_block_desc_1* Desc;
int age;
__main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_8d9d27_mi_1,age);
}
static struct __main_block_desc_1 {
size_t reserved;
size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};
struct __main_block_impl_2 {
struct __block_impl impl;
struct __main_block_desc_2* Desc;
int age;
__main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_2(struct __main_block_impl_2 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_8d9d27_mi_3,age);
}
static struct __main_block_desc_2 {
size_t reserved;
size_t Block_size;
} __main_block_desc_2_DATA = { 0, sizeof(struct __main_block_impl_2)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
int age = 10;
void(*block2)(void) = ((void (*)())&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA, age));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_8d9d27_mi_2,((Class (*)(id, SEL))(void *)objc_msgSend)((id)block1, sel_registerName("class")) ,((Class (*)(id, SEL))(void *)objc_msgSend)((id)block2, sel_registerName("class")),((Class (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA, age)), sel_registerName("class")));
}
return 0;
}
3.2 block三种内存布局
iOS程序的内存布局如下
- 代码段:编译之后的代码
- 数据段
- 字符串常量:比如NSString *str = @"123"
- 已初始化数据:已初始化的全局变量、静态变量等
- 未初始化数据:未初始化的全局变量、静态变量等
- 栈:函数调用开销,比如局部变量。分配的内存空间地址越来越小
- 堆:通过alloc、malloc、calloc等动态分配的空间,分配的内存空间地址越来越大
三种block的内存分配如下
3.3 block类型确定
block类型 | 产生原因 |
---|---|
__NSGlobalBlock__ |
没有访问auto变量 |
__NSStackBlock__ |
访问了auto变量 |
__NSMallocBlock__ |
__NSStackBlock__ 调用了copy |
int main(int argc, const char * argv[]) {
@autoreleasepool {
//global类型,因为没有访问auto变量
void(^block1)(void) = ^(){
NSLog(@"block1");
};
int age = 10;
//__NSMallocBlock__类型,因为访问auto变量,但是arc自动调用了copy
void(^block2)(void) = ^(){
NSLog(@"block2===%d",age);
};
//第三个class调用auto变量
NSLog(@"%@ %@ %@",[block1 class] ,[block2 class],[^{
NSLog(@"%d",age);
}class]);
}
return 0;
}
执行结果
2020-11-02 22:28:15.039022+0800 Blocktest[7396:97958] __NSGlobalBlock__ __NSMallocBlock__ __NSStackBlock__
Program ended with exit code: 0
__NSStackBlock__
问题
超如作用域,函数栈自动销毁,当访问作用域变量,由于销毁会发生错乱
mrc 设置方法
在Build Settings
搜索automatic reference
如下图,改为NO即可
[图片上传失败...(image-eff3b4-1605196903148)]
mrc运行代码
void (^block)(void);
void test2()
{
// NSStackBlock
int age = 10;
block = ^{
NSLog(@"block---------%d", age);
} ;
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
test2();
block();
}
return 0;
}
运行结果,发现结果错乱
2020-11-02 23:06:51.888443+0800 Blocktest[11938:153696] block----------272632760
Program ended with exit code: 0
所以arc自动copy,把block放到堆上,把他变成__NSMallocBlock__
类型,有程序员自己决定是否销毁,由于arc帮我们做了内存的管理,自动添加了release
总结
这也解释了开发中我们对block
使用copy
属性
4.block的copy
在arc环境下,编译器会根据情况自动将粘上的block复制到堆上,所属情况如下
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
4.1 block作为函数返回值时
编写代码
arc环境下
typedef void(^LYJBlock)(void);
LYJBlock lyjBlock(){
int a = 10;
return ^{
NSLog(@"block内部==%d",a);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
LYJBlock block = lyjBlock();
NSLog(@"类型===%@",[block class]);
block();
}
return 0;
}
打印正常
2020-11-05 22:05:04.972907+0800 Block-copy[85340:714362] 类型===__NSMallocBlock__
2020-11-05 22:05:04.973409+0800 Block-copy[85340:714362] block内部==10
Program ended with exit code: 0
这是因为编译器自动进行了copy操作
mrc环境可以看到编译报错
4.2 将block赋值给__strong指针时
修改代码
int a = 10;
LYJBlock block = ^{
NSLog(@"block内部==%d",a);
};
NSLog(@"类型===%@",[block class]);
block();
执行结果
2020-11-05 22:08:35.371983+0800 Block-copy[85872:720914] 类型===__NSMallocBlock__
2020-11-05 22:08:35.372516+0800 Block-copy[85872:720914] block内部==10
Program ended with exit code: 0
这是因为block对象被block强指针指着
mrc环境执行为
20-11-05 22:15:53.232727+0800 Block-copy[86746:734403] 类型===__NSStackBlock__
2020-11-05 22:15:53.233273+0800 Block-copy[86746:734403] block内部==10
Program ended with exit code: 0
4.3 block作为Cocoa API中方法名含有usingBlock的方法参数时
例如
NSArray *array = @[];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
4.4 block作为GCD API的方法参数时
例如:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
5.内存管理
5.1 auto变量捕获对象类型细节
修改代码为
arc环境下
typedef void(^LYJBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
// int temp = 10;
// static int temp1 = 10;
// void(^block)(int,int) = ^(int a ,int b){
// NSLog(@"这是一个block,---%d",temp);
// NSLog(@"这是一个block,---%d",temp1);
// NSLog(@"这是一个block,---%d",temp_);
// NSLog(@"这是一个block,---%d",temp1_);
// };
// temp = 20;
// temp1 = 20;
// temp_ = 20;
// temp1_ = 20;
// block(10,10);
LYJBlock block ;
{
Student *stu = [Student new];
stu.name = @"10";
block = ^{
NSLog(@"-----block内部==%@",stu.name);
};
}
NSLog(@"---------");//断点行
}
return 0;
}
在打断点处发现student没有释放,原因
简化代码
Student *stu = [Student new];
stu.name = @"10";
LYJBlock block = ^{
NSLog(@"-----block内部==%@",stu.name);
};
// }
NSLog(@"---------");
clang查看c++代码 ,这次加上arc 和runtime进行
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
int temp_ = 10;
static int temp1_ = 10;
typedef void(*LYJBlock)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Student *__strong stu;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Student *__strong _stu, int flags=0) : stu(_stu) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Student *__strong stu = __cself->stu; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_925dfe_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)stu, sel_registerName("name")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->stu, (void*)src->stu, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->stu, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LYJBlock block ;
{
Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)stu, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_925dfe_mi_0);
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, stu, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_925dfe_mi_2);
}
return 0;
}
原因:
__main_block_impl_0
里有Student
类型的变量指针stu
,所以block强引用了student对象,在断点处不会释放
从block角度深层次理解:
mrc环境下
LYJBlock block ;
{
Student *stu = [Student new];
stu.name = @"10";
block = ^{
NSLog(@"-----block内部==%@",stu.name);
};
[stu release];
}
NSLog(@"---------");//断点处
执行结果
2020-11-05 22:46:51.163798+0800 Blocktest[90785:792303] student被释放
(lldb)
此时的block为stack类型的block也就是栈上的block,不会对对象强引用;但是对block执行copy操作,就不会执行释放。这是疑问执行copy操作后,变成堆上的block,内部会执行 [person retain]操作,使student引用计数增加1
不论mrc还是arc栈空间的block都不会强引用外部的变量
修改代码为
LYJBlock block ;
{
Student *stu = [Student new];
stu.name = @"10";
__weak Student *weakStu = stu;
block = ^{
NSLog(@"-----block内部==%@",weakStu.name);
};
}
NSLog(@"---------");
[图片上传失败...(image-6940b5-1605196903148)]
clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m
为
int temp_ = 10;
static int temp1_ = 10;
typedef void(*LYJBlock)(void);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Student *__weak weakStu;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Student *__weak _weakStu, int flags=0) : weakStu(_weakStu) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Student *__weak weakStu = __cself->weakStu; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_40ef2f_mi_1,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)weakStu, sel_registerName("name")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakStu, (void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
LYJBlock block ;
{
Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)stu, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_40ef2f_mi_0);
__attribute__((objc_ownership(weak))) Student *weakStu = stu;
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakStu, 570425344));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_40ef2f_mi_2);
}
return 0;
}
提前释放了,
因为block没有强引用stu,弱引用stu,所以arc大括号结束,stu释放了。为什么若引用就提前释放,因为弱引用
_Block_object_assign
函数会根据你是弱引用,不会对stu机型引用计数加1,
而强引用会_Block_object_assign
会对stu对象进行引用计数加1,所以不会释放
总结
当block内部访问了对象类型的auto变量时
- 如果block是在栈上,将不会对auto变量产生强引用
- 因为在栈上,自己什么时候销毁都不确定,所以没必要强应用别的对象
- 如果block被拷贝到堆上
- 会调用block内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
- 如果block从堆上移除
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的auto变量(release
5.1.1 易错点
iOS工程
#import "LYJPerson.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
LYJPerson *p = [[LYJPerson alloc] init];
__weak LYJPerson *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", p);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-------%@", weakP);
});
});
NSLog(@"touchesBegan:withEvent:");
}
执行结果
2020-11-06 00:02:17.931720+0800 Interview03-测试[95455:877623] touchesBegan:withEvent:
2020-11-06 00:02:18.931785+0800 Interview03-测试[95455:877623] 1-------
2020-11-06 00:02:18.932006+0800 Interview03-测试[95455:877623] LYJPerson - dealloc
2020-11-06 00:02:20.932116+0800 Interview03-测试[95455:877623] 2-------(null)
可以看到1s后执行,对象销毁,2s打印对象为空
系统函数会对block执行copy到堆上,block对变量强引用,所以不会马上释放,1s后,block释放,所以强引用的block释放,对象释放,2s后,再次访问对象,已经释放
修改代码
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
LYJPerson *p = [[LYJPerson alloc] init];
__weak LYJPerson *weakP = p;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@", weakP);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2-------%@", p);
});
});
NSLog(@"touchesBegan:withEvent:");
}
执行结果
2020-11-06 00:12:47.961397+0800 Interview03-测试[96023:890589] touchesBegan:withEvent:
2020-11-06 00:12:49.059968+0800 Interview03-测试[96023:890589] 1-------
2020-11-06 00:12:51.060395+0800 Interview03-测试[96023:890589] 2-------
2020-11-06 00:12:51.060665+0800 Interview03-测试[96023:890589] LYJPerson - dealloc
系统函数会对block执行copy到堆上,block对变量强引用,所以不会马上释放,1s后,访问弱引用,不会引用计数产生影响,2s强应用才产生影响,所以3s后释放
5.2 __block
修饰符
int类型变量,在block内部修改时,会提示错误,如下
由2.1可知block
内部执行逻辑在__main_block_func_0
中,而局部变量是在main
,所以无法修改
修改方法
- 变成static,原因从2.2可知,指针传递捕获变量
- 变成全局变量,原因2.3可知原因
-
__block
修饰
方法1和2弊端是,会一直在内存中,所以一般采用第三种方案
方案3我们通过clang查看c++代码为简称:BlockA
struct __Block_byref_temp_0 {
void *__isa;
__Block_byref_temp_0 *__forwarding;
int __flags;
int __size;
int temp;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_temp_0 *temp; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_temp_0 *_temp, int flags=0) : temp(_temp->__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_temp_0 *temp = __cself->temp; // bound by ref
(temp->__forwarding->temp) = 30;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_2ba61f_mi_0,(temp->__forwarding->temp));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->temp, (void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_temp_0 temp = {(void*)0,(__Block_byref_temp_0 *)&temp, 0, sizeof(__Block_byref_temp_0), 10};
void(*block)(int,int) = ((void (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_temp_0 *)&temp, 570425344));
(temp.__forwarding->temp) = 20;
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 10);
}
return 0;
}
查看c++代码可知,
temp被包装成了
__Block_byref_temp_0 *temp
对象,成为了一个指针对象,对象里有一个int变量temp
定义temp的地方变成
__attribute__((__blocks__(byref))) __Block_byref_temp_0 temp = {(void*)0,(__Block_byref_temp_0 *)&temp, 0, sizeof(__Block_byref_temp_0), 10};
简化后为__Block_byref_temp_0 temp = { 0, &temp, 0, sizeof(__Block_byref_temp_0), 10}
定义block部分可简化为
void(*block)(int,int) = &__main_block_impl_0( __main_block_func_0, &__main_block_desc_0_DATA, &temp, 570425344));
将第2步定义的结构体地址传入给__main_block_impl_0
的temp
指针修改地方
(temp->__forwarding->temp) = 30
,通过__forwarding
指针修改temp
变量
5.2.1扩展1
当修改代码为
__block int temp = 10;
__block NSObject * obj = [NSObject new];
void(^block)(int,int) = ^(int a ,int b){
temp = 30;
obj = nil;
NSLog(@"这是一个block,---%d",temp);
};
temp = 20;
block(10,10);
}
对应
struct __Block_byref_temp_0 {
void *__isa;
__Block_byref_temp_0 *__forwarding;
int __flags;
int __size;
int temp;
};
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_temp_0 *temp; // by ref
__Block_byref_obj_1 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_temp_0 *_temp, __Block_byref_obj_1 *_obj, int flags=0) : temp(_temp->__forwarding), obj(_obj->__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_temp_0 *temp = __cself->temp; // bound by ref
__Block_byref_obj_1 *obj = __cself->obj; // bound by ref
(temp->__forwarding->temp) = 30;
(obj->__forwarding->obj) = __null;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_b33668_mi_0,(temp->__forwarding->temp));
}
5.2.2 扩展2
如果此时最后打印temp的地址是谁的地址呢
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int temp = 10;
__block NSObject * obj = [NSObject new];
void(^block)(int,int) = ^(int a ,int b){
temp = 30;
obj = nil;
NSLog(@"这是一个block,---%d",temp);
};
temp = 20;
block(10,10);
NSLog(@"%p", &temp);
}
return 0;
}
是__Block_byref_temp_0
中的int变量temp
的地址,猜测苹果屏蔽内部实现,给开发者感知他的类型没变,给kvo类似
总结
-
__block
可以用于解决block内部无法修改auto变量值的问题
*__block
不能修饰全局变量、静态变量(static) - 编译器会将__block变量包装成一个对象
5.3内存管理
当block在栈上时,并不会对__block变量产生强引用
-
当block被copy到堆时
- 会调用block内部的copy函数
- copy函数内部会调用
_Block_object_assign
函数 -
_Block_object_assign
函数会对__block变量形成强引用(retain)
copy的流程如下
- block0和block1分别指向__block变量
- block0进行指向__block的时候,arc进行copy,会将block0拷贝到堆上,并且将指向的__block也拷贝到堆上
- block1进行指向__block的时候,arc进行copy,会将block1拷贝到堆上,由于__block已经拷贝到堆上,所以不再拷贝
- forwading指针原来指向自己栈上
- 复制到堆上后,栈上的block的fording指针指向堆上block,当然堆上的block还是指向堆上
也解释了为什么要设计成forwarding指针这种模式
当block从堆中移除时
- 会调用block内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放引用的__block变量(release)
分为以下两个情况
情况1,只有一个引用,__block没有持有者而被废弃
情况2,block0被废弃的时候,会对__block变量进行release操作,是的__block引用计数减1,但是还有被应用,所以没有被销毁,当block1页销毁的时候,也对__block变量进行release操作,他的引用计数也减1,这时候减少为0,也就是没持有者,所以__block此时进行销毁
5.3.1 __block和对象类型比较
编写代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int temp = 10;
NSObject *obj = [NSObject new];
__weak NSObject *weakObj = obj;
void(^block)(void) = ^(void){
NSLog(@"%d",temp);
NSLog(@"%@",weakObj);
};
block();
}
return 0;
}
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m 操作后
struct __Block_byref_temp_0 {
void *__isa;
__Block_byref_temp_0 *__forwarding;
int __flags;
int __size;
int temp;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__weak weakObj;
__Block_byref_temp_0 *temp; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *__weak _weakObj, __Block_byref_temp_0 *_temp, int flags=0) : weakObj(_weakObj), temp(_temp->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_temp_0 *temp = __cself->temp; // bound by ref
NSObject *__weak weakObj = __cself->weakObj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_03f8f3_mi_0,(temp->__forwarding->temp));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_03f8f3_mi_1,weakObj);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->temp, (void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_temp_0 temp = {(void*)0,(__Block_byref_temp_0 *)&temp, 0, sizeof(__Block_byref_temp_0), 10};
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
__attribute__((objc_ownership(weak))) NSObject *weakObj = obj;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakObj, (__Block_byref_temp_0 *)&temp, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
通过
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->temp, (void*)src->temp, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakObj, (void*)src->weakObj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
- 看到__block类型是block直接对temp对象强引用
- 对象类型,是根据对象是weak类型还是strong类型,进行强引用还是弱引用
5.3.2 对象类型的auto变量、__block变量总结
- 当block在栈上时,对它们都不会产生强引用
- 当block拷贝到堆上时,都会通过copy函数来处理它们
- __block变量(假设变量名叫做a)
- _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
- 对象类型的auto变量(假设变量名叫做p)
- _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
- 当block从堆上移除时,都会通过dispose函数来释放它们
- __block变量(假设变量名叫做a)
- _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);
- 对象类型的auto变量(假设变量名叫做p)
- _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);
5.3.3 被__block修饰的对象类型
- 当__block变量在栈上时,不会对指向的对象产生强引用
- 当__block变量被copy到堆时
- 会调用__block变量内部的copy函数
- copy函数内部会调用_Block_object_assign函数
- _Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
- 如果__block变量从堆上移除
- 会调用__block变量内部的dispose函数
- dispose函数内部会调用_Block_object_dispose函数
- _Block_object_dispose函数会自动释放指向的对象(release)
准备Student代码,方便观看是否销毁
@implementation Student
- (void)dealloc
{
NSLog(@"我销毁了==%s",__func__);
}
@end
编写测试代码
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Student *stu = [Student new];
void(^block)(void) = ^(void){
NSLog(@"stu===%p",stu);
};
block();
}
return 0;
}
clang之后的代码为
struct __Block_byref_stu_0 {
void *__isa;
__Block_byref_stu_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Student *__strong stu;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_stu_0 *stu; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_stu_0 *_stu, int flags=0) : stu(_stu->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_stu_0 *stu = __cself->stu; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_098af5_mi_0,(stu->__forwarding->stu));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->stu, (void*)src->stu, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->stu, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_stu_0 stu = {(void*)0,(__Block_byref_stu_0 *)&stu, 33554432, sizeof(__Block_byref_stu_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"))};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_stu_0 *)&stu, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
此时的内存的stu对象指向关系为
- 通过
__main_block_impl_0
和__Block_byref_stu_0
知道 - block内部有一个
__Block_byref_stu_0
类型的stu指针 - stu指针指向的结构体里有一个跟外部引用类型一致的stu对象
- 该stu对象指向我们在栈上new查出来的对象
此时修改代码为弱引用
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [Student new];
__block __weak Student *weakStu = stu;
void(^block)(void) = ^(void){
NSLog(@"stu===%p",weakStu);
};
block();
}
return 0;
}
c++代码变为
struct __Block_byref_weakStu_0 {
void *__isa;
__Block_byref_weakStu_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Student *__weak weakStu;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_weakStu_0 *weakStu; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_weakStu_0 *_weakStu, int flags=0) : weakStu(_weakStu->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
此时的结构就变成
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
__attribute__((__blocks__(byref))) __attribute__((objc_ownership(weak))) __Block_byref_weakStu_0 weakStu = {(void*)0,(__Block_byref_weakStu_0 *)&weakStu, 33554432, sizeof(__Block_byref_weakStu_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, stu};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_weakStu_0 *)&weakStu, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
struct __Block_byref_weakStu_0 {
void *__isa;//8个字节
__Block_byref_weakStu_0 *__forwarding;//8个字节
int __flags;//4个字节
int __size;//4个字节
void (*__Block_byref_id_object_copy)(void*, void*);//8个字节
void (*__Block_byref_id_object_dispose)(void*);//8个字节
Student *__weak weakStu;
};
具体流程是
- block拷贝到堆上时,调用block内部的Desc的
__main_block_copy_0
进行copy操作,进行强引用结构体__Block_byref_weakStu_0
-
__Block_byref_weakStu_0
被拷贝到堆上时,会调用它内部的__Block_byref_id_object_copy
进行copy,对指向的对象进行强或者弱引用 -
__Block_byref_id_object_copy_131
偏移40个字节,也就是Student *__weak weakStu
指向了我们的变量weakStu
销毁时
- 会调用__block变量内部的dispose函数,对weakStu进行释放
- weakStu释放时候,其指向的
__Block_byref_weakStu_0
也会进行自己内部的__Block_byref_id_object_dispose
,对其指向的对象进行release操作
5.4循环引用
测试代码Student
#import
NS_ASSUME_NONNULL_BEGIN
typedef void(^LYJBlock)(void);
@interface Student : NSObject
@property (nonatomic, copy) LYJBlock block;
@property (nonatomic, assign) int age;
@end
NS_ASSUME_NONNULL_END
#import "Student.h"
@implementation Student
- (void)dealloc
{
NSLog(@"我销毁了==%s",__func__);
}
@end
在main函数中
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [Student new];
stu.age = 10;
stu.block = ^(void){
NSLog(@"age===%d",stu.age);
};
}
NSLog(@"-----1111111----");
return 0;
}
执行结果
2020-11-12 21:02:59.422862+0800 BLockTest[7117:97280] ---------
Program ended with exit code: 0
按道理执行到11111时候,大括号结束,stu应该释放,但是打印可看到没有释放,造成了循环引用。如图所示
- stu指向Student对象,Student对象里有一个成员变量_block,改block指向了block变量,block里又有一个strong类型的指针指向Student对象,如图形成了循环引用
- 大括号结束后,左下角的stu指向右边的Student的指针消失,但是右边还存在着相互引用,所以不会销毁
5.4.1 ARC环境循环引用解决
- 用
__weak
、__unsafe_unretained
解决 - 用
__block
解决(必须要调用block)
最好方案是__weak
原因:
-
__weak
:不会产生强引用,指向的对象销毁时,会自动让指针置为nil -
__unsafe_unretained
:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变
——weak
方式
修改代码
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [Student new];
stu.age = 10;
__weak Student *weakStu = stu;
// __weak typeof(stu) weakStu = stu;//两种写法等效
stu.block = ^(void){
NSLog(@"age===%d",weakStu.age);
};
}
NSLog(@"----1111111111----");
return 0;
}
执行结果
2020-11-12 21:27:13.093870+0800 BLockTest[8878:125076] 我销毁了==-[Student dealloc]
2020-11-12 21:27:13.094279+0800 BLockTest[8878:125076] ----1111111111----
Program ended with exit code: 0
clang一下查看源码为
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Student *__weak weakStu;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Student *__weak _weakStu, int flags=0) : weakStu(_weakStu) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Student *__weak weakStu = __cself->weakStu; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_e1adf2_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakStu, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakStu, (void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)stu, sel_registerName("setAge:"), 10);
__attribute__((objc_ownership(weak))) Student *weakStu = stu;
((void (*)(id, SEL, LYJBlock _Nonnull))(void *)objc_msgSend)((id)stu, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakStu, 570425344)));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_e1adf2_mi_1);
return 0;
}
可以看到此时block内部的stu对象为弱引用,此时的结构图如图
执行完大括号后,左边引用的stu指针销毁,右边的_block指向block也销毁,相应的block也销毁
__unsafe_unretained
方式
修改代码
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [Student new];
stu.age = 10;
__unsafe_unretained typeof(stu) weakStu = stu;
stu.block = ^(void){
NSLog(@"age===%d",weakStu.age);
};
}
NSLog(@"----1111111111----");
return 0;
}
执行结果
2020-11-12 21:34:08.318599+0800 BLockTest[9301:132968] 我销毁了==-[Student dealloc]
2020-11-12 21:34:08.319057+0800 BLockTest[9301:132968] ----1111111111----
Program ended with exit code: 0
查看c++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Student *__unsafe_unretained weakStu;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Student *__unsafe_unretained _weakStu, int flags=0) : weakStu(_weakStu) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
Student *__unsafe_unretained weakStu = __cself->weakStu; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_abec9f_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakStu, sel_registerName("age")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakStu, (void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakStu, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
Student *stu = ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"));
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)stu, sel_registerName("setAge:"), 10);
__attribute__((objc_ownership(none))) typeof(stu) weakStu = stu;
((void (*)(id, SEL, LYJBlock _Nonnull))(void *)objc_msgSend)((id)stu, sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, weakStu, 570425344)));
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_abec9f_mi_1);
return 0;
}
可以发现block内部的变量为__unsafe_unretained
类型,也不会产生强引用,此时的内存结构图也是
释放过程和weak
类似
不一样的点在于
-
weak
方式在指向的对象销毁时,会自动让指针置为nil,也就是block销毁时,weakStu指针会自动变成nil -
__unsafe_unretained
则指向的对象销毁时,指针存储的地址值不变,也就是block销毁时,weakStu指针地址不变,会不安全,再次访问weakStu会造成野指针异常
__block
方式
修改代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Student *stu = [Student new];
stu.age = 10;
stu.block = ^(void){
NSLog(@"age===%d",stu.age);
stu = nil;
};
stu.block();
}
NSLog(@"----1111111111----");
return 0;
}
细节点在于block内部stu = nil;
以及stu.block();
这两个地方缺一不可
原因:
clang查看c++代码
struct __Block_byref_stu_0 {
void *__isa;
__Block_byref_stu_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Student *__strong stu;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_stu_0 *stu; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_stu_0 *_stu, int flags=0) : stu(_stu->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_stu_0 *stu = __cself->stu; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_508bae_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)(stu->__forwarding->stu), sel_registerName("age")));
(stu->__forwarding->stu) = __null;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->stu, (void*)src->stu, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->stu, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_stu_0 stu = {(void*)0,(__Block_byref_stu_0 *)&stu, 33554432, sizeof(__Block_byref_stu_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Student *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Student"), sel_registerName("new"))};
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(stu.__forwarding->stu), sel_registerName("setAge:"), 10);
((void (*)(id, SEL, LYJBlock _Nonnull))(void *)objc_msgSend)((id)(stu.__forwarding->stu), sel_registerName("setBlock:"), ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_stu_0 *)&stu, 570425344)));
((LYJBlock (*)(id, SEL))(void *)objc_msgSend)((id)(stu.__forwarding->stu), sel_registerName("block"))();
}
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6c_hf7xv8yx5m379l6zdpb8nkgwsk1rzq_T_main_508bae_mi_1);
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
此时的内存结构为
当执行block()方法时候,如下gif所示,所以会释放
5.4.2 MRC环境循环引用解决
- 用
__unsafe_unretained
解决 - 用
__block
解决
mrc环境不支持__weak
用__unsafe_unretained
跟arc环境类似
用__block
解决
#import "Student.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block Student *stu = [Student new];
stu.age = 10;
stu.block = ^(void){
NSLog(@"age===%d",stu.age);
};
[stu release];
}
NSLog(@"----1111111111----");
return 0;
}
执行结果
2020-11-12 22:14:44.792007+0800 BLockTest[11955:180623] 我销毁了==-[Student dealloc]
2020-11-12 22:14:44.792391+0800 BLockTest[11955:180623] ----1111111111----
Program ended with exit code: 0
这是因为在5.3.3的
_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain)
也就是__Block_byref_stu_0变量内部的`Student * stu不会对student对象进行retain操作
也就是此时的内存结构是
5.5 @weakify、@strongify实现原理
在平时开发中,我们也会遇到@weakify、@strongify,这两个关键字是RAC中避免Block循环引用而开发的2个宏,这2个宏的实现过程很牛,值得我们学习
源码
#define weakify(...) \
rac_keywordify \
metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")
5.5.1 weakify
#if DEBUG
#define rac_keywordify autoreleasepool {}
#else
#define rac_keywordify try {} @catch (...) {}
#endif
这里在debug模式下使用@autoreleasepool是为了维持编译器的分析能力,而使用@try/@catch 是为了防止插入一些不必要的autoreleasepool。rac_keywordify 实际上就是autoreleasepool {}
的宏替换。因为有了autoreleasepool {}的宏替换,所以weakify要加上@,形成@autoreleasepool {}。
#define metamacro_foreach_cxt(MACRO, SEP, CONTEXT, ...) \
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(__VA_ARGS__))(MACRO, SEP, CONTEXT, __VA_ARGS__)
__VA_ARGS__
:总体来说就是将左边宏中 ... 的内容原样抄写在右边 __VA_ARGS__
所在的位置。它是一个可变参数的宏,是新的C99规范中新增的,目前似乎只有gcc支持(VC从VC2005开始支持)。
那么我们使用@weakify(self)
传入进去。__VA_ARGS__
相当于self
。此时我们可以把最新开始的weakify
套下来。于是就变成了这样:
rac_weakify_,, __weak, __VA_ARGS__
整体替换MACRO, SEP, CONTEXT, ...
这里需要注意的是,源码中就是给的两个","逗号是连着的,所以我们也要等效替换参数,相当于SEP是空值。
替换完成之后就是下面这个样子:
autoreleasepool {}
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self))(rac_weakify_, , __weak, self)
现在我们需要弄懂的就是metamacro_concat
和 metamacro_argcount
是干什么用的。
继续看看metamacro_concat
的实现
#define metamacro_concat(A, B) \
metamacro_concat_(A, B)
#define metamacro_concat_(A, B) A ## B
##
是宏连接符。举个例子:
假设宏定义为#define XNAME(n) x##n,代码为:XNAME(4),则在预编译时,宏发现XNAME(4)与XNAME(n)匹配,则令 n 为 4,然后将右边的n的内容也变为4,然后将整个XNAME(4)替换为 x##n,亦即 x4,故 最终结果为 XNAME(4) 变为 x4。所以A##B就是AB。
metamacro_argcount 的实现
#define metamacro_argcount(...) \
metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
#define metamacro_at(N, ...) \
metamacro_concat(metamacro_at, N)(__VA_ARGS__)
metamacro_concat是上面讲过的连接符,那么metamacro_at, N = metamacro_atN,由于N = 20,于是metamacro_atN = metamacro_at20。
#define metamacro_at0(...) metamacro_head(__VA_ARGS__)
#define metamacro_at1(_0, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at3(_0, _1, _2, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at4(_0, _1, _2, _3, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at5(_0, _1, _2, _3, _4, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at6(_0, _1, _2, _3, _4, _5, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at7(_0, _1, _2, _3, _4, _5, _6, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at8(_0, _1, _2, _3, _4, _5, _6, _7, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at9(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at10(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at11(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at12(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at13(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at14(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at15(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at16(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at17(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at18(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at19(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, ...) metamacro_head(__VA_ARGS__)
#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)
metamacro_at20的作用就是截取前20个参数,剩下的参数传入metamacro_head。
#define metamacro_head(...) \
metamacro_head_(__VA_ARGS__, 0)
#define metamacro_head_(FIRST, ...) FIRST
metamacro_head的作用返回第一个参数。返回到上一级metamacro_at20,如果我们从最源头的@weakify(self),传递进来,那么metamacro_at20(self,20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1),截取前20个参数,最后一个留给metamacro_head_(1),那么就应该返回1。
metamacro_concat(metamacro_foreach_cxt, metamacro_argcount(self)) = metamacro_concat(metamacro_foreach_cxt, 1) 最终可以替换成metamacro_foreach_cxt1。
在源码中继续搜寻。
// metamacro_foreach_cxt expansions
#define metamacro_foreach_cxt0(MACRO, SEP, CONTEXT)
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
#define metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) \
SEP \
MACRO(1, CONTEXT, _1)
#define metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
metamacro_foreach_cxt2(MACRO, SEP, CONTEXT, _0, _1) \
SEP \
MACRO(2, CONTEXT, _2)
#define metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
metamacro_foreach_cxt3(MACRO, SEP, CONTEXT, _0, _1, _2) \
SEP \
MACRO(3, CONTEXT, _3)
#define metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
metamacro_foreach_cxt4(MACRO, SEP, CONTEXT, _0, _1, _2, _3) \
SEP \
MACRO(4, CONTEXT, _4)
#define metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
metamacro_foreach_cxt5(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4) \
SEP \
MACRO(5, CONTEXT, _5)
#define metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
metamacro_foreach_cxt6(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5) \
SEP \
MACRO(6, CONTEXT, _6)
#define metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
metamacro_foreach_cxt7(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6) \
SEP \
MACRO(7, CONTEXT, _7)
#define metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
metamacro_foreach_cxt8(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7) \
SEP \
MACRO(8, CONTEXT, _8)
#define metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
metamacro_foreach_cxt9(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8) \
SEP \
MACRO(9, CONTEXT, _9)
#define metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
metamacro_foreach_cxt10(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9) \
SEP \
MACRO(10, CONTEXT, _10)
#define metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
metamacro_foreach_cxt11(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) \
SEP \
MACRO(11, CONTEXT, _11)
#define metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
metamacro_foreach_cxt12(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11) \
SEP \
MACRO(12, CONTEXT, _12)
#define metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
metamacro_foreach_cxt13(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12) \
SEP \
MACRO(13, CONTEXT, _13)
#define metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
metamacro_foreach_cxt14(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13) \
SEP \
MACRO(14, CONTEXT, _14)
#define metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
metamacro_foreach_cxt15(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14) \
SEP \
MACRO(15, CONTEXT, _15)
#define metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
metamacro_foreach_cxt16(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15) \
SEP \
MACRO(16, CONTEXT, _16)
#define metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
metamacro_foreach_cxt17(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16) \
SEP \
MACRO(17, CONTEXT, _17)
#define metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
metamacro_foreach_cxt18(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17) \
SEP \
MACRO(18, CONTEXT, _18)
#define metamacro_foreach_cxt20(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19) \
metamacro_foreach_cxt19(MACRO, SEP, CONTEXT, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18) \
SEP \
MACRO(19, CONTEXT, _19)
metamacro_foreach_cxt这个宏定义有点像递归,这里可以看到N 最大就是20,于是metamacro_foreach_cxt19就是最大,metamacro_foreach_cxt19会生成rac_weakify_(0,__weak,18),然后再把前18个数传入metamacro_foreach_cxt18,并生成rac_weakify(0,__weak,_17),依次类推,一直递推到metamacro_foreach_cxt0。
#define metamacro\_foreach\_cxt0(MACRO, SEP, CONTEXT)
metamacro_foreach_cxt0就是终止条件,不做任何操作了。
于是最初的@weakify就被替换成
autoreleasepool {}
metamacro_foreach_cxt1(rac_weakify_, , __weak, self)
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
代入参数
autoreleasepool {}
rac_weakify_(0,__weak,self)
最终需要解析的就是rac_weakify_
#define rac_weakify_(INDEX, CONTEXT, VAR) \
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
把(0,__weak,self)的参数替换进来(INDEX, CONTEXT, VAR)。
INDEX = 0, CONTEXT = __weak,VAR = self,
于是
CONTEXT __typeof__(VAR) metamacro_concat(VAR, _weak_) = (VAR);
//等效替换为
__weak __typeof__(self) self_weak_ = self;
最终@weakify(self) = __weak __typeof__(self) self_weak_ = self;
这里的self_weak_ 就完全等价于我们之前写的weakSelf。
5.5.2 strongify
rac_keywordify还是和weakify一样,是autoreleasepool {},只为了前面能加上@
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
_Pragma("clang diagnostic pop")
strongify比weakify多了这些_Pragma语句。
关键字_Pragma是C99里面引入的。_Pragma比#pragma(在设计上)更加合理,因而功能也有所增强。
上面的等效替换
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshadow"
#pragma clang diagnostic pop
这里的clang语句的作用:忽略当一个局部变量或类型声明遮盖另一个变量的警告。
最初的
#define strongify(...) \
rac_keywordify \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
metamacro_foreach(rac_strongify_,, __VA_ARGS__) \
_Pragma("clang diagnostic pop")
strongify里面需要弄清楚的就是metamacro_foreach
和 rac_strongify_
。
#define metamacro_foreach(MACRO, SEP, ...) \
metamacro_foreach_cxt(metamacro_foreach_iter, SEP, MACRO, __VA_ARGS__)
#define rac_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
我们先替换一次,SEP = 空 , MACRO = rac_strongify_ , VA_ARGS , 于是替换成这样。
metamacro_foreach_cxt(metamacro_foreach_iter,,rac_strongify_,self)
根据之前分析,metamacro_foreach_cxt再次等效替换,metamacro_foreach_cxt##1(metamacro_foreach_iter,,rac_strongify_,self)
根据
#define metamacro_foreach_cxt1(MACRO, SEP, CONTEXT, _0) MACRO(0, CONTEXT, _0)
再次替换成metamacro_foreach_iter(0, rac_strongify_, self)
继续看看metamacro_foreach_iter的实现
#define metamacro_foreach_iter(INDEX, MACRO, ARG) MACRO(INDEX, ARG)
最终替换成rac_strongify_(0,self)
#define rac_strongify_(INDEX, VAR) \
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
INDEX = 0, VAR = self,于是@strongify(self)就等价于
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
//等价于
__strong __typeof__(self) self = self_weak_;
注意@strongify(self)只能使用在block中,如果用在block外面,会报错,因为这里会提示你Redefinition of 'self'。
5.5.3 总结
@weakify(self) = @autoreleasepool{} __weak __typeof__ (self) self_weak_ = self;
@strongify(self) = @autoreleasepool{} __strong __typeof__(self) self = self_weak_;
经过分析以后,其实@weakify(self) 和 @strongify(self) 就是比我们日常写的weakSelf、strongSelf多了一个@autoreleasepool{}而已,至于为何要用这些复杂的宏定义来做,目前我还没有理解。如果有大神指导其中的原因,还请多多指点