iOS底层原理研究所移动开发作家群(719776724)分享专题

Block内部的数据结构类型

2019-02-21  本文已影响49人  大兵布莱恩特

本文会记录下最近对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.png

OC的方法能够使用到 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
上一篇下一篇

猜你喜欢

热点阅读