Block内存关系
Block经典问题循环引用&解决
Block底层分析
Block底层HooK
1. 研究工具:clang
为了研究编译器是如何实现 block 的,我们需要使用 clang。clang 提供一个命令,可以将 Objetive-C 的源码改写成 c 语言的,借此可以研究 block 具体的源码实现方式
首先cd到代码文件目录,然后执行clang命令
clang -rewrite-objc main.m
int main(int argc, char * argv[]) {
@autoreleasepool {
typedef void (^blk_t)(void);
blk_t block = ^{ printf("Hello, World!\n");
};
block();
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }
}
如果我们引入了UIKit框架,那么此命令会报错#import <UIKit/UIKit.h>报错
main.m:10:9: fatal error: 'UIKit/UIKit.h' file not found
好事多磨啊,这是因为我们没有指定链接的sdk
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator12.1.sdk main.m

2 实现分析
这里只选取部分关键代码。
不难看出int main(int argc, char * argv[]) 就是主函数的实现。
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
typedef void (*blk_t)(void);
blk_t block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
}
其中,__main_block_impl_0是block的一个C++的实现(最后面的_0代表是main中的第几个block),也就是说也是一个结构体。
(1) __main_block_impl_0
__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;
}
};
这里impl.isa
的类型为_NSConcreteStackBlock
,由于 clang 改写的具体实现方式和 LLVM 不太一样,所以这里 isa 指向的还是_NSConcreteStackBlock。但在 LLVM 的实现中,开启 ARC 时,block 应该是 _NSConcreteGlobalBlock 类型。
(2) __block_impl
其中__block_impl的定义如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
其结构体成员如下:
- isa,指向所属类的指针,也就是block的类型
- flags,标志变量,在实现block的内部操作时会用到
- Reserved,保留变量
- FuncPtr,block执行时调用的函数指针
可以看出,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象(runtime里面,对象和类都是用结构体表示)。
(3) __main_block_desc_0
是block的描述信息 的结构体
,__main_block_desc_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)};
其结构成员含义如下:
- reserved:保留字段
- Block_size:block大小(sizeof(struct __main_block_impl_0))
以上代码在定义__main_block_desc_0结构体时,同时创建了__main_block_desc_0_DATA,并给它赋值,以供在main函数中对__main_block_impl_0进行初始化。
(4) __main_block_func_0
如上的main函数中,__main_block_func_0也是block的一个C++的实现,是block要执行的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello, World!\n");
}
(5) 综合可知:
- __main_block_impl_0的isa指针指向了_NSConcreteStackBlock。
- 从main函数的main.cpp中看,__main_block_impl_0的FuncPtr指向了函数__main_block_func_0。
- __main_block_impl_0的Desc也指向了定义__main_block_desc_0时就创建的__main_block_desc_0_DATA,其中纪录了block结构体大小等信息。
上面block代码,没有获取任何外部变量,应该是 _NSConcreteGlobalBlock类型的。该类型的block和函数一样 存放在 代码段 内存段。内存管理简单。
2.1 使用自动变量的Block分析
int main(){
int age = 100;
const char *name = "zyt";
void (^blk) (void) = ^{
printf("block invoke age = %d age = %s", age, name);
};
age = 101;
blk();
return 0;
}
使用clang -rewrite-objc main.m
命令重写为C++语法的源文件。对比上一个的数据结构方法和调用步骤大体上是一样的,只针对变化的地方进行分析
__main_block_impl_0
结构体的变化
增加了两个成员:int类型的age成员、char*类型的name成员,用于保存外部变量的值,在初始化的时候就会使用自动变量的值初始化这两个成员的值
调用的变化
__main_block_func_0函数的参数struct __main_block_impl_0 *__cself在这个例子有使用到了,因为两个自动变量对应的值被保存在__main_block_impl_0结构体中了,方法中有使用到这两个变量,直接从__main_block_impl_0结构体中获取这两个值,但是这两个值是独立于自动变量的存在了
由上面的分析,可以得出如下的结论:使用变量的Block调用本质上是使用函数指针调用函数,参数是保存在block的结构体中的,并且保存的是值而不是引用
2.2 使用__block修饰的变量的Block分析
Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。进而在block内部也可以修改外部变量的值
// 源代码
int main(int argc, const char * argv[]) {
__block int age = 100;
const char *name = "zyt";
void (^blk) (void) = ^{
age += 2;
printf("block invoke age = %d age = %s", age, name);
};
age += 1;
blk();
}
使用clang -rewrite-objc main.m命令重写为C++语法的源文件,对结果分析如下的注释,该转换后的代码做了些许的调整,包括代码的缩进和添加了结构体声明,使得该代码可以直接运行
// clang改写后的代码如下,以下代码是经过调整可以直接运行的
struct __main_block_desc_0;
// __block_impl结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
// __block修饰的变量对应的结构体,里面包含了该变量的原始值也就是age成员,另外还有一个奇怪的__forwarding成员,稍后我们会分析它的用处
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
// __main_block_impl_0结构体包含了四个成员__block_impl类型的impl成员、__main_block_desc_0类型的Desc成员、__Block_byref_age_0 *类型的age成员、char类型的name成员;一个构造方法__main_block_impl_0,构造方法中初始化impl成员、Desc成员、age成员和name成员,比起上一个结构体的变化是age的类型变为了包装自动变量的结构体了
struct __main_block_impl_0 {
__block_impl impl;
__main_block_desc_0 Desc;
const char *name;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp,
struct __main_block_desc_0 *desc,
const char *_name,
__Block_byref_age_0 *_age,
int flags=0) :
name(_name),
age(_age->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// Block执行的方法
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
const char *name = __cself->name; // bound by copy
(age->__forwarding->age) += 2;
printf("block invoke age = %d age = %s", (age->__forwarding->age), name);
}
// Block拷贝函数
static void __main_block_copy_0(struct __main_block_impl_0dst, struct __main_block_impl_0src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/BLOCK_FIELD_IS_BYREF/);
}
// Block销毁函数
static void __main_block_dispose_0(struct __main_block_impl_0src) {
_Block_object_dispose((void)src->age, 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};
// 重写后的入口函数main
int main(int argc, const char * argv[]) {
attribute((blocks(byref))) __Block_byref_age_0 age = {
(void*)0,
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
100};
const char *name = "zyt";
struct __main_block_impl_0 blockImpl =
__main_block_impl_0((void *)__main_block_func_0,
&__main_block_desc_0_DATA,
name,
(__Block_byref_age_0 )&age,
570425344);
void (blk) (void) = ((void ()())&blockImpl);
(age.__forwarding->age) += 1;
((void ()(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
__block修饰自动变量转换为C++代码的结构体关系图:

该重写的代码大部分和上面分析过的例子代码是类似,发现增加了两个处理方法Block拷贝函数__main_block_copy_0
和Block销毁函数__main_block_dispose_0
,这两个函数会保存在__main_block_desc_0
结构体的copy和dispose成员中;另外添加了一个__Block_byref_age_0
结构体类型用户处理__block修饰的自动变量。以下针对这两点从源代码的角度进行一个分析
__main_block_copy_0
方法中调用到的_Block_object_assign
可以在 runtime.c 这里找到 ,主要看下_Block_object_assign
方法里面的处理逻辑
- flags参数值为8,是BLOCK_FIELD_IS_BYREF枚举对应的值,会走到
_Block_byref_assign_copy
方法的调用步骤 -
_Block_byref_assign_copy
方法会在在堆上创建Block_byref
对象,也就是Block对象,并且把栈上和堆上的Block对象的forwarding属性值都修改为指向堆上的Block对象,这样使用两个对象的修改值都会修改为同一个地方
栈上的__block
自动变量__forwarding
指向关系以及拷贝到堆上之后__forwarding
指向关系如下图所示

网友评论