block底层原理(出道文章)
以后就选择掘金作为文章发表平台了。希望自己多多学习,多多产出。
什么是block
- block本质是一个OC对象,因为它内部有一个isa指针
- block封装了函数调用以及函数调用环境的OC对象。因为block会捕获执行代码以及变量
block底层代码
OC代码如下
int main(int argc, const char * argv[]) {
void (^block)(int) = ^(int a){
a = 12;
printf("%d\n", a);
};
return 0;
}
使用pxcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件 将OC文件转译为C++文件,代码如下
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, int a) {
a = 12;
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size; //block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
/**
__main_block_impl_0是一个函数指针,即第22849行的 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)。
参数1:函数指针:(void *)__main_block_func_0:第22856行的函数,内部封装的在block内实际写的代码
参数2: 结构体指针:__main_block_desc_0, 第22861行。
*/
void (*block)(int) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
int b = 2;
((void (*)(__block_impl *, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, b);
}
return 0;
}
block底层其实是一个结构体,结构体名字叫 __函数名_block_impl_序号。解释一下,我们是在main函数定义的一个block,所以这个block的底层结构体叫 __main_blcok_impl_0,如果是在main函数里定义的第二个block,其底层结构体名字就叫 __mian_block_impl_1。如果在 test 函数里面定义了一个block,其底层结构体名字会是什么? ·__test_block_impl_0
__mian_block_impl_0 结构体
struct __main_block_impl_0 {
struct __block_impl impl; //结构体
struct __main_block_desc_0* Desc; //结构体指针,指向一个结构体,内部两个成员变量, flags不用管,block_size表示当前block的大小
__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 __block_impl impl;
指向 __block_impl结构体指针。
struct __block_impl {
void *isa; //指向block类对象的isa指针
int Flags; //默认在初始化时会被设置为0
int Reserved; //
void *FuncPtr; //函数地址,该函数地址的函数内代码就是block里面写的代码
};
struct __main_block_desc_0* Desc
结构体指针:
static struct __main_block_desc_0 {
size_t reserved; //默认为0,不知道是什么
size_t Block_size; //block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; //初始化函数,直接赋予其block结构体的大小
结构体初始化函数 __main_block_impl_0(**void** *fp, **struct** __main_block_desc_0 *desc, **int** flags=0)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock; //指向block的类型
impl.Flags = flags; //flag默认为0
impl.FuncPtr = fp; //封装block代码函数的函数指针
Desc = desc; //desc结构体
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) {
a = 12;
}
block的变量捕获
block变量捕获机制
- 局部变量:捕获值
- 静态变量:捕获指针
- 全局变量:不捕获,直接访问
block对变量捕获 auto变量 对底层结构体的影响
__main_block_impl_0结构体会增加捕获的变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a; //注意这里
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
block变量捕获 对象变量 对底层结构体的影响
1.__main_block_impl_0结构体会增加捕获的 变量的指针
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *obj; //注意这里
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
2.在 __main_block_desc_0结构体中增加 copy 方法和 dispose 方法。这两个方法会在后面的 block捕获对象内存管理 中说到
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};
block变量捕获 __block修饰的变量 对底层结构体的影响
1.不论 __block 修饰的是 auto变量 还是 对象变量。都会把 变量 封装成一个 __Block_byref 对象,并添加到 __main_block_impl_0中。
__Block_byref结构体详情
struct __Block_byref_c_0 {
void *__isa; //有isa,说明这是一个OC对象
__Block_byref_c_0 *__forwarding; //forwarding指针在栈上是指向自己,当拷贝到堆上时,栈上的block的forwarding指针指向堆
int __flags; //标志位
int __size; //结构体大小
int c; //实际的变量
};
调用时传入的东西
__attribute__((__blocks__(byref))) __Block_byref_c_0 c = {(void*)0,(__Block_byref_c_0 *)&c, 0, sizeof(__Block_byref_c_0), 10};
__main_block_impl_0结构体变化
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_c_0 *c; // by ref 注意这里
__Block_byref_d_1 *d; // by ref 注意这里
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_c_0 *_c, __Block_byref_d_1 *_d, int flags=0) : c(_c->__forwarding), d(_d->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
2.在 __main_block_desc_0结构体中增加 copy 方法和 dispose 方法
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};
总结
1.block在捕获 auto变量 时仅仅会在 __main_block_impl_0 中添加一个该 auto变量对象
2.block在捕获 对象变量 的时候会在 __main_block_impl_0 中添加改对象指针,并且在 __main_block_desc_0 中添加 copy 和 dispose 方法。
3.__block修饰符会将 变量 (不管是 auto 还是 对象变量) 都会将变量变成 __block_byref 对象。同时在 __main_block_desc_0 结构体中添加 copy dispose 方法
__block对象内存管理
-
block在捕获对象变量时会根据对象的修饰符(
__strong、__weak、_unsafe_retain)来对该对象进行引用计数管理。如果是strong就引用计数+1。 -
block捕获变量之后默认是在栈上的,在MRC环境下执行copy方法,或者ARC环境下编译器自动将其拷贝到堆上。
- 会在
__main_block_desc_0结构体中添加copy和dispose方法。 - 在栈上的时候会增加引用计数,copy的时候会调用 copy 方法将block拷贝到堆上,引用计数会再次+1
- 如果是被
__block修饰过的__block_byref对象,block在栈上的时候__block_byref的__forwarding指针是指向自己,但是在拷贝到堆上的时候,会使用 copy 函数,将block拷贝到堆上,同时将 栈上block的__forwarding指针指向堆上的block。 - 堆上的block在销毁的时候会调用
dispose方法销毁。
- 会在