Block内部的数据结构类型
本文会记录下最近对B lock 的一些探究,先从 block 是如何对局部变量捕获开始讲起.
image.png上边的代码 auto变量的值改变时,block 内部输出结果还是10,而 static 的局部变量 值变成了50. 那么 block 内部是如何对age height 进行处理的呢?
可以通过 xcrun -sdk phones clang -arch arm64 -rewrite-objc main.c -o main.cpp 将OC代码转成C++代码实现
void testBlock() {
auto int age = 10;
static int height = 100;
void(*block)(void) = &__testBlock_block_impl_0(
&__testBlock_block_func_0,
&__testBlock_block_desc_0_DATA, age, &height));
age = 20;
height = 50;
block->FuncPtr(block);
__testBlock_block_func_0 block实现部分的代码
__testBlock_block_desc_0_DATA block 内存占用大小 描述信息
__testBlock_block_impl_0 构造方法 返回一个 __testBlock_block_impl_0 结构体对象 并且取地址 复制给 *block 变量
block->FuncPtr 实质就是 block->impl.FuncPtr 由于结构体内部第一个成员就是该结构体的内存地址 ,所以 __testBlock_block_impl_0 可以看成 __block_impl 因为它的指针地址就是指向第一个成员 __block_impl的地址 ,所以通过强制转换后 可以 block->FuncPtr(block); 调用 block
}
上边的代码还可以简化为
void testBlock() {
auto int age = 10;
static int height = 100;
void(*block)(void) = &__testBlock_block_impl_0(__testBlock_block_func_0, &__testBlock_block_desc_0_DATA, age, &height));
age = 20;
height = 50;
block->FuncPtr(block); 调用 block对应的函数指针
__testBlock_block_func_0 ///block 代码的实现 相当于一个函数变量
}
我们写的block 最终被转成一个 struct __testBlock_block_impl_0 结构体 ,并将 block 快代码方法实现 封装到一个 __block_impl 结构体里的 void *FuncPtr;
以下对 block 的数据类型进行详细的说明
struct __testBlock_block_impl_0 {
struct __block_impl impl; ///block函数回调 和 调用环境
struct __testBlock_block_desc_0* Desc; ///描述了 block 占用多少内存
int age; ///对局部变量age的捕获
int *height; ///对局部变量height的捕获
///结构体构造方法 会对结构体内部所有成员进行赋值 相当于 init方法
__testBlock_block_impl_0(void *fp, struct __testBlock_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock; ///block 内部 isa 指针 block 实质就是一个OC对象 封装了函数调用和函数调用的环境
impl.Flags = flags;
impl.FuncPtr = fp; /// 函数指针 我们 block 里边的实现代码 都保存到这个函数地址里 ,将来执行 block 代码块 就是执行函数指针指向的函数实现
Desc = desc; ///对 block 大小的描述
}
};
auto变量 随用随开用完即销 因此 block 捕获 auto 变量时 只把值传递进去进来了保存 ,当 auto 变量销毁的时候 也不影响 block 内部对这个 auto 变量的使用.
static局部变量 并不随着函数作用域结束而销毁 它会一直存在内存中直到程序声明周期结束才释放掉,因此 block 对局部静态变量的捕获是以指针地址进行传递,由于是指针赋值而不是值传递 所以 height 改变时 block 内部输出的值时最新的 height 的值.
这个函数封装了 block代码的实现 即 ^{
NSLog(@"age = %d height = %d",age,height);
};
由FuncPtr 函数指针调用该函数
static void __testBlock_block_func_0(struct __testBlock_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_hj_pwgsq9614nb0vq4zd315tcx80000gn_T_main_a95244_mi_0,age,(*height));
}
还可以将 block 强制转换成 __block_impl 结构体 进行函数的调用
image.png输出结果跟 block() 是一样的
由于 block 内部会对局部变量进行捕获 所以就会造成循环引用的问题 ,比如 self 持有 block ,而 block 内部会使用到 self ,这种情况下就会造成 block 和OC对象内存泄露问题
image.pngOC的方法能够使用到 self _cmd 是因为有两个 匿名参数 id self , SEL _cmd ,所以 self 在方法里边也是一个局部的变量 这个时候 block 就会捕获这个 self ,而 self 又持有 block 因此造成了循环引用 我们通过C++代码一探究竟
image.png image.png可以看出来 block 内部有一个Person *self 的指针 , 在给 block 赋值的时候 将 self 的内存地址传递进去 ,这样就造成了 互相持有,所以 block 使用过程中需要注意循环引用的问题
好了,我是大兵布莱恩特,欢迎加入博主技术交流群,iOS 开发交流群
QQ20180712-0.png