Block(下)- 底层源码分析

323 阅读4分钟

前言

通过block上篇的学习,在上层我们已经知道了block的三种表现形式和关于引用计数以及循环引用相关的理解,通过这篇将进一步去了解他底层的实现,内部到底是什么结构?怎么去copy,如何捕获保存以及释放?

cpp文件分析

block在如果有外部变量进来的时候,会自从生成相应的成员变量并且给内部的成员变量赋值。

截屏2021-08-25 上午10.54.17.png

无成员变量时

截屏2021-08-25 上午10.54.58.png

去掉强转类型以后的代码,第一步函数声明,第二步,函数式调用保存。也就是说,如果函数不调用,是不会保存的。

截屏2021-08-25 上午11.02.23.png

看一下具体的函数实现,可以看到编译阶段的block是栈block,而我没有进行弱引用访问了外部的变量,应该是堆block才对,猜测应该是在运行阶段作了处理。 截屏2021-08-25 上午11.02.07.png

如果要对外界的变量作修改,需要加__block,否则,内部生成的变量a与外界变量a不是同一个地址。那么加了__block有什么作用呢? 截屏2021-08-25 上午11.27.44.png

生成了一个a,类型是__Block_byref_a_0 截屏2021-08-25 上午11.30.39.png

__Block_byref_a_0是一个结构体。 截屏2021-08-25 上午11.29.57.png

函数声明里用的就是这个结构体本身的地址。 截屏2021-08-25 上午11.30.16.png

所以在进行值修改的时候,这里修改的就是他自己的值,修改的是同一片内存空间。 截屏2021-08-25 上午11.31.01.png

源码分析

找到源码工程

通过打断点。

截屏2021-08-25 下午2.19.50.png

截屏2021-08-25 下午2.20.13.png

找到是在libSystem,但是并没有开源,但是不代表找不到,可以在libclosure-79中找到源码。 截屏2021-08-25 下午2.21.55.png

找到block_copy,如果是释放状态或者全局都是直接对block返回,如果是栈block,那么就需要copy一份,因为在编译器作开辟空间的操作,会加大了编译器的负担。然后全部拷贝一份,isa指向变成堆block截屏2021-08-25 下午2.40.06.png

看一下block_layout结构,看一下主要的几个参数表示

isa指向,是栈block还是堆block还是全局block。 flags标识符 invoke函数调用 截屏2021-08-25 下午2.40.14.png

为何栈block会变成堆block?

之前看到,本来是堆block在编译时进来是栈block,那么做了什么操作呢?通过寄存器先看一下copy前后的打印。

截屏2021-08-25 下午2.55.03.png

截屏2021-08-25 下午2.58.03.png

v8@?0的意思可以通过打印签名[NSMethodSignature signatureWithObjCTypes:"v8@?0"]

number of arguments当前参数1个,返回空。

flagsblock类型的对象

memory8个字节。

为什么要签名?

因为在invoke的时候,他也是 一种消息发送的机制,这种消息发送跟之前的消息转发是一样的,最后一步也会走到签名,关于方法生效或者异常的情况,所以需要签名。

Block_descriptor

看一下正常的情况如果使用了外部的变量。会存在copydispose,这些都记录在Block_descriptor1中,根据情况不同,展示的也不同,具体是通过内存平移的方式去获得Block_descriptor2或者Block_descriptor3.因为此处的内存是连续的,所以可以平移获取。

截屏2021-08-25 下午3.54.07.png

截屏2021-08-25 下午3.46.55.png

__block指针拷贝的补充

如果只是普通的对象进来,那么内部生成成员变量跟外界的捕获的成员变量不是同一个,虽然他们指向同一片空间,值相同。如果是__block修饰的对象,并且满足BLOCK_BYREF_HAS_COPY_DISPOSE,也就是捕获到了外界的变量,开辟了堆内存并且copy,拿到了变量以后,通过byref_keep对整个变量生命周期的持有,防止当变量释放,整个block也释放了。

截屏2021-08-25 下午4.16.09.png

   **struct** Block_byref_2 {

    // requires BLOCK_BYREF_HAS_COPY_DISPOSE

    BlockByrefKeepFunction byref_keep; //= __Block_byref_id_object_copy_131

    BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131

};

block对外部变量的捕获流程。

分析捕获变量的情况,主要看三种。

第一种是object对象类型,直接指针指向,第二种是block类型,对本身block的捕获,第三种byref类型。

截屏2021-08-25 下午5.28.11.png

官方注释说明。 截屏2021-08-25 下午5.28.20.png

如果是__block修饰的变量,再被捕获进来以后,除了正常的进行block从栈到堆的copy以外,还会生成相应的__Block_byref_id_object_copy方法(第二层拷贝),这个方法里会在走_Block_object_assign方法在进行一次拷贝(第三层拷贝)。

截屏2021-08-26 下午3.17.02.png

Block内部结构的一些结构体定义解释整理

Block_layout结构体成员含义如下:

  • isa: 指向所属类的指针,也就是block的类型

  • flags: 标志变量,在实现block的内部操作时会用到

  • Reserved: 保留变量

  • invoke: block执行时调用的函数指针,block内部的执行代码都在这个函数中

  • descriptor: block的详细描述,包含 copy/dispose 函数,处理block引用外部变量时使用

  • variables: block范围外的变量,如果block没有调用任何外部变量,该变量就不存在

Block_descriptor结构体成员含义如下:

  • reserved: 保留变量
  • size: block的内存大小
  • copy: 拷贝block中被 __block 修饰的外部变量
  • dispose: 和 copy 方法配置应用,用来释放资源

__block修饰的变量被封装成了一个对象 __Block_byref_a_0 成员含义如下:

  • __isa: 指向所属类的指针
  • __forwarding: 指向对象在堆中的拷贝
  • __flags: 标志变量,在实现block的内部操作时会用到
  • __size: 对象的内存大小
  • a: 原始类型的变量