通过窥探Block
的底层实现,解答以下问题
1.Block
底层数据结构是什么,本质是什么
2.Block
与其所访问的外部变量的关系
3.Block
的内存管理
Block的本质是什么?是函数?代码块?OC对象?
简单起见,我们在main.m
文件中写一个没有任何参数访问的简单的Block
,通过分析其源码窥探Block
的本质
定义一个简单的Block
typedef void(^MyBlock)(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
block = ^{
NSLog(@"this is block");
};
}
return 0;
}
我们知道OC是基于C/C++实现的,接下来我们通过命令行xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名
将main.m
文件转化成对应的.cpp
文件,窥探Block
的本质。
第一步:在.cpp
文件中检索int main(int argc, const char * argv[])
定位main
函数位置,从而找到我们写的代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock block;
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
}
return 0;
}
为了方便阅读我们将上述源码中的核心部分((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
中的强制类型转换去掉,可以得到&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)
,到此可以清楚的看到这里调用了一个函数__main_block_impl_0(...)
,传入了两个参数__main_block_func_0
和__main_block_desc_0_DATA
。并将函数返回值的地址赋值给了block
。接下来我们分别分析函数及这两个参数。
首先我们看下函数__main_block_impl_0
在.cpp
文件中进一步检索函数__main_block_impl_0
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;
}
};
通过上述源码,可以看到__main_block_impl_0(...)
是结构体struct __main_block_impl_0
的构造函数。也就是说Block
的本质就是结构体struct __main_block_impl_0
继续检索传入的参数__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d9b536_mi_0);
}
可以看出这就是我们写的NSLog(@"this is block");
即block的实现部分。
继续检索__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)};
分析上述源码可知,这是一个存储了结构体struct __main_block_impl_0
大小信息等描述信息的结构体。
分析完入参后,我们再一次将目光回到结构struct __main_block_impl_0
上
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;
}
};
进一步分析其成员变量,其中struct __main_block_desc_0
我们上边已经看过了。接下来我们检索成员变量struct __block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
可与看到一个很熟悉的指针isa
指针,我们知道oc对象的基本特点就是有一个isa
指针,看到这你可能会猜想block
本身是否也是一个oc
对象呢?
答案是肯定的,block
本质的确是一个oc
对象,下面我们将通过其他方式验证这一点。我们知道几乎所有的oc
对象都继承自NSObject
,尝试打印Block
的父类。
NSLog(@"block class : %@", [block class]);
NSLog(@"block super class : %@", class_getSuperclass([block class]));
NSLog(@"block super super class : %@", class_getSuperclass(class_getSuperclass([block class])));
NSLog(@"block super super super class : %@", class_getSuperclass(class_getSuperclass(class_getSuperclass([block class]))));
// 输出log
block class : __NSGlobalBlock__
block super class : __NSGlobalBlock
block super super class : NSBlock
block super super super class : NSObject
从上述log可以看出,block
本身的确是一个oc
对象,且其继承自NSObject
Block对基础数据类型的捕获
定义三种基本类型变量,并在Block
中访问他们
int globalInt = 1; // 全局变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
static int staticInt = 5; // 静态变量
int autoInt = 10; // 自动变量(本质上是auto int autoInt = 10;省略了关键字auto,通常我们定义的都是这种变量)
block = ^{
NSLog(@"staticInt = %d", staticInt);
NSLog(@"autoInt = %d", autoInt);
NSLog(@"globalInt = %d", globalInt);
};
block();
}
return 0;
}
通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名
得到对应的.cpp
文件。
在.cpp
文件中检索int main(int argc, const char * argv[])
定位main
函数
int globalInt = 1;
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock block;
static int staticInt = 5;
int autoInt = 10;
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticInt, autoInt));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
观察上述源码,可以看到在构建block
的结构体struct __main_block_impl_0
时,比之前多传了两个参数&staticInt
和autoInt
,但是并没有传globalInt
,即:static变量传了地址(地址传递),局部auto变量传了值(值传递),全局变量没有传递。
接下来我们去struct __main_block_impl_0
中看下这些参数的处理
观察上述代码,可以看到结构体struct __main_block_impl_0
中多了两个成员变量int *staticInt;
和int autoInt;
,并且构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticInt, int _autoInt, int flags=0) : staticInt(_staticInt), autoInt(_autoInt)
将外边传过来的_staticInt
和_autoInt
分别赋值给了int *staticInt;
和int autoInt;
。也就是说Block分别对静态变量和自动变量进行了地址捕获和值捕获
在看下__main_block_func_0
和__main_block_desc_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *staticInt = __cself->staticInt; // 取结构体中的int型指针变量staticInt给局部int型指针变量staticInt
int autoInt = __cself->autoInt; // 取结构体中int型变量autoInt给局部int型变量autoInt
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_0, (*staticInt)); // 打印局部int型指针staticInt指向值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_1, autoInt);//打印局部变量autoInt的值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_7c19b4_mi_2, globalInt);//打印全局变量globalInt的值
}
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
没有变化
小结
- Block不会捕获全局变量
- Block会对静态变量进行地址捕获
- Block会对自动变量进行值捕获
那么Block为什么这么做呢,认真思考下就会发现这是一个很合乎情理的设计,对于全局变量由于它是全局的,本身就可以在任意地方访问,所以Block自然不需要捕获它,就像函数中访问全局变量不需要将其当参数传进去一样;对于局部静态变量,首先我们想象一种使用场景,Block及局部静态变量是在一个普通函数中的,并且Block作为函数返回值使用,在这种情况下,因为出了函数我们就无法访问到局部变量了,所以首先Block肯定要对这个静态局部变量进行捕获,再者我们知道静态变量不会随着大括号即函数的结束而销毁,所以Block只需要对其进行地址捕获即可。对于普通的自动局部变量,由于其会被销毁,所以需要对其进行值捕获
Block对OC对象的捕获
为了演示,我们先创建一个类LJPerson
@interface LJPerson : NSObject
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) int age;
@end
和基础变量一样,我们也创建三种不同的LJPerson变量,并在Block中访问他们
typedef void(^MyBlock)(void);
LJPerson *globalPerson; // 全局变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block;
globalPerson = [[LJPerson alloc] init];
globalPerson.name = @"globalPerson";
globalPerson.age = 1;
static LJPerson *staticPerson; // 静态变量
staticPerson = [[LJPerson alloc] init];
staticPerson.name = @"staticPerson";
staticPerson.age = 10;
LJPerson *autoPerson = [[LJPerson alloc] init]; // 自动变量
autoPerson.name = @"autoPerson";
autoPerson.age = 20;
block = ^{
NSLog(@"globalPerson = %@", globalPerson.name);
NSLog(@"staticPerson = %@", staticPerson.name);
NSLog(@"autoPerson = %@", autoPerson.name);
};
block();
}
return 0;
}
同样的生成对应的.cpp
文件,并检索main函数int main(int argc, const char * argv[])
和结构体struct __main_block_impl_0
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
MyBlock block;
globalPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)globalPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_0);
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)globalPerson, sel_registerName("setAge:"), 1);
static LJPerson *staticPerson;
staticPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)staticPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_1);
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)staticPerson, sel_registerName("setAge:"), 10);
LJPerson *autoPerson = ((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LJPerson"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)autoPerson, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_6m_csw1z8qs7csb3nvkwyrlbnm80000gp_T_main_d7ed57_mi_2);
((void (*)(id, SEL, int))(void *)objc_msgSend)((id)autoPerson, sel_registerName("setAge:"), 20);
block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticPerson, autoPerson, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
LJPerson **staticPerson; // 地址捕获
LJPerson *autoPerson; // 值捕获
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, LJPerson **_staticPerson, LJPerson *_autoPerson, int flags=0) : staticPerson(_staticPerson), autoPerson(_autoPerson) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
观察上述源码,可知Block对OC变量的捕获机制和基础数据类型一样。即:Block对变量的捕获方式只取决于其是全局变量、静态变量、还是自动变量。
那么Block对OC类型变量的处理与基础数据类型变量的处理究竟有什么不同呢?
检索Block的成员变量struct __main_block_desc_0
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};
对比之前的源码,可以发现这里多了两个函数copy
,dispose
。
观察结构体对象__main_block_desc_0_DATA
的入参,可知copy
、dispose
函数分别对应参数__main_block_copy_0
、__main_block_dispose_0
,检索函数__main_block_copy_0
、__main_block_dispose_0
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->staticPerson, (void*)src->staticPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->autoPerson, (void*)src->autoPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->staticPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->autoPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}
观察函数两个函数,可以看到__main_block_copy_0
内部分别对staticPerson
、autoPerson
对象各调用了一次_Block_object_assign
方法,__main_block_dispose_0
内部分别对staticPerson
、autoPerson
各调用了一次_Block_object_dispose
方法。也就是说Block除了捕获OC对象的外,还会对OC对象做内存管理。
小尾巴
到此我们已经解决了开篇中的第1、2个问题,并且知道了Block
会对OC对象做内存管理,那么在后续的文章里我们将继续探索Block
是如何进行内存管理的以及当我们用__weak
、__block
修饰变量时究竟是在做些什么
网友评论