Block终究是什么,咱们先从c++代码开始

从一个最简单的block结构开始

Block原理(一)

clang -rewrite-objc main.m -o main.cpp && open main.cpp

Block原理(一)

Block原理(一)

为了便利阅览 咱们简化一下代码

Block原理(一)

为了便利进一步阅览,这儿对其中的命名做了简化,参阅下面的简单流程

Block原理(一)

  • 结合clang编译中心c++代码,经过block的创立,结合上图, 脑子里先勾勒一个sketch

    • 创立两层结构

      • BlockCreate 结构

      • Block结构,属于BlockCreate的成员

    • 经过BlockCreate结构传参,实例化BlockCreate成员Block::block

    • 最终回来的是一个 BlockCreate结构指针

    • 经过BlockCreate结构首地址,咱们能够拿到成员Block::block, BlockCreate首地址与 成员block首地址是相同的,因为block位于 BlockCreate内存空间的开始处

      已然能够拿到首地址(成员block地址),那么相同能够经过内存偏移拿到成员Desc的地址

    • 经过拿到成员Block::block地址,就能够调用Block::block的成员办法FuncPtr了, 而FuncPtr恰恰是经过 BlockCreate结构实例化Block::block成员时 赋值的fun函数进口地址

  • 一定要了解这个两层结构,尽管不是真实含义源码,但是对后边咱们剖析源码很有帮助

前面的比如没有使用变量,咱们能够经过前面的办法再操作一次,比照下差异

Block原理(一)

Block原理(一)

当自界说的结构体内部拜访外部的一个局部变量时

你会发现clang生成的c++代码发生了改变 对照上面的那个实例化流程

  • BlockCreate 结构体内多了一个成员 int a

  • BlockCreate结构 也多了一个传参

  • func函数内部拜访变量 a经过 func(BlockCreate *self) 的 BlockCreate::*self 来获取复制

  • 你会发现有3处存在变量a

    • main函数内的局部变量a

    • BlockCrete 结构体内的成员变量a

    • func办法内部的局部变量a

    其实这3个变量a分别是3个不同的变量了

把局部变量a改为static润饰,持续clang c++检查

Block原理(一)

Block原理(一)

用static润饰变量a,不相同了

BlockCreate结构传参,此刻传递的是 a的地址,而BlockCreate成员 a也变成了 指针, func内部的局部变量a 也变成了 指针,func内部的a是经过 BlockCreate::*self 的指针a 赋值 给func内部的局部变量 指针a

所以static润饰a后,func内部拜访的a其实还是 main函数内部的 指针a

把局部变量a改为 __block润饰,持续clang c++检查

Block原理(一)

Block原理(一)

希望你不会觉得懵,这次杂乱了些

  • 呈现了一个结构 __Block_byref_a_0

  • BlockCreate 成员Desc的结构内部多了两个 函数 copy & dispose

这儿简单解释下

  • 普通的局部变量a 变成了一个结构 __Block_byref_a_0, a是这个结构的成员

    • 成员 void *__isa

    • 成员 __block_byref_a_0 *__forwarding;

    • 成员 int __flags;

    • 成员 int __size

    • 成员 int a

    在main里声明的__block润饰的局部变量, 地址赋值给了 __forwarding, 值赋给了 Block_byref结构里的成员a,留意这个设定, 尽管成员也叫a,仅仅起到一个接纳值的作用,关键在于__forwarding 拿到了原来的a的指针

    先看下__block润饰的a终究是怎样拜访的

Block原理(一)

__forwarding 类型 __Block_byref_a_0 *,类似于链表节点,
所以也是一个指向 __Block_byref_a_0 结构的指针 至于有什么用,
暂存疑,后边源码接着剖析
比照着看,其实很明显,不难了解

Block原理(一)

block源码 – libclosure-79 检查

源码进口该怎样检查呢,咱们先经过汇编看下

Block原理(一)

已然retainBlock,阐明block拓荒了空间,进入检查

Block原理(一)

持续跳转 br x16

Block原理(一)

现在找到了_Block_copy这样一个符号,然后进入源码检查

Block原理(一)

你会看到一个结构Block_layout

Block原理(一)

Block_layout 便是前面经过clang c++代码 剖分出的 两层结构BlockCreate成员 Block::block

__block 润饰变量 测验代码放进 block源码进行调试

Block原理(一)

这段代码是在block源码中测验的

Block原理(一)

这其实便是按照Block_layout 栈上的空间结构,在堆区创立了一个Block_layout结构

一起 新拓荒的Block_layout结构->invoke 从原来栈上Block_layout->invoke复制过来

Block原理(一)

已然是堆上拓荒空间创立的Block_layout结构,天然isa 指向 _NSConcreteMallocBlock (堆block)

block剖析源码遇到问题

现在还有两块没探究到源码,便是 前面经过clang 编译生成的c++代码中__Block_byref_a_0这样的结构,还有一块是BlockCreate结构逻辑部分

那么接下来该何去何从?

我挑选最原始的办法 汇编 + 下符号断点 + 结合clang c++代码剖析

Block原理(一)

先把代码断到此处,防止dyld其他流程搅扰

Block原理(一)

下符号断点 一起把前面剖析过的 _Block_copy 符号也下下来,为了便利剖析流程

跟着调试 进入 _Block_object_dispose:

Block原理(一)

回到之前clang编译出的c++代码看下

Block原理(一)

已然下到了符号_Block_object_dispose 那么相同也把符号 _Block_object_copy下下来持续调试

没有的话 就试试 _Block_object_assign, 之所以没有找到 _Block_object_copy符号,是因为那是由编译器决议的

成功断点符号 _Block_object_assign

Block原理(一)

找到条理,天然咱们又回到了源码

Block原理(一)

  • 看下源码注释

    When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point to do the assignment.

    当Blocks(能够了解为前面的有成员func的那个结构) 或许 Block_byref持有方针时分,这个进口就会被触发 履行赋值操作

Block原理(一)

  • __block int a = 10 类型为 BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK or BLOCK_FIELD_IS_BYREF

    履行 _Block_byref_copy()

_Block_byref_copy

在剖析_Block_byref_copy流程之前,咱们需求了解下Block_byref 是什么

Block原理(一)

从前面clang编译拿到的c++代码,能够看到,Block_byref 是对常规变量的封装,封装结构里还多了isa,__forwarding成员

Block原理(一)

源码中还存在 Block_byref_2 Block_byref_3 两个结构,暂且不表,后边会持续阐明

咱们能够做个假设,现在咱们测验的实例 是block引证外部 __block润饰的变量,咱们也是这么用的,已然block内部拜访外部变量,那么也会对于这个变量的引证计数产生影响 flags便是存储引证计数的

_Block_byref_copy翻译

Block原理(一)

如果源byref结构已经在heap上,则不需求履行复制,引证计数+1

Block原理(一)

中心有一段内存偏移的代码,还没解析,持续

从源码中咱们看到

Block_byref_2 *src2 = src + 1
Block_byref_3 *src3 = src2 + 2

那么 Block_byref Block_byref_2 Block_byref_3 是接连的内存结构,依据条件判别是否解析 Block_byref_2 Block_byref_3

认知遗留问题

找遍了源码 clang编译出的c++代码里 __main_block_impl_0 这样的结构并没有发现

Block原理(一)

byref_keep byref_destroy 终究完成了什么功用

因为咱们用的常规变量a测验 咱们换成object看下

将变量a换为object测验

Block原理(一)

clang c++代码

Block原理(一)

Block原理(一)

从源码得知

Block原理(一)

编译阶段,Block_byref结构 flag被设置为 1 << 25, 标识是有 Block_byref_2结构的

Block原理(一)

Block原理(一)

131有什么含义

Block原理(一)

两个参数 + 40 什么意思

Block原理(一)

按照编译的逻辑,byref_keep 便是 object类型的方针的 复制

但是运行时会做批改 流程有不同

相同 byref_destroy:

Block原理(一)

以上为 Block_byref 逻辑,再经过clang得到的c++ 看下 Block_layout 的处理

Block原理(一)

Block原理(一)

再确认下 __block润饰的 object方针,在block体里 终究是怎么拜访的

Block原理(一)

总结

  • __block 润饰变量之后,编译器会在栈上构建一个 栈Block_byref(包括变量指针)

  • 界说block,能够了解为编译器生成一个中心结构BlockCreate(这个姓名是特意起的,知道是个结构,为了便于了解,你能够这么了解)

    • 一起编译器会在栈上初始化构建一个 栈Block_layout(包括func成员)
  • 履行BlockCreate结构办法

    • 经过Block_layout首地址偏移 得到 Block_copy函数地址, 履行Block_copy,把 栈Block_byref 复制 到堆Block_byref

    • 结构参数 栈Block_byref,经过Block_byref首地址偏移 得到 Block_byref_2(包括_Block_byref_copy 即byref复制函数)首地址, 履行 _Block_byref_copy函数, 把栈Block_byref 复制到 堆Block_byref

    • 持续上一步的位置 内存偏移 8字节,得到堆上拓荒的 object内存空间首地址, 这儿当然就存放 object方针了

    • 需求留意的一个细节 栈Block_byref 复制到 堆Block_byref之后,因为堆上是新的内存空间,那么栈与堆不就两个空间了吗,怎么保证拜访的是同一块内存?

      就觉办法便是,在复制之后, 把 栈Block_byref 和 堆Block_byref 里的forwarding 都指向了 堆Block_byref, 也便是 堆 forwarding再指向一遍自己

      __block润饰变量之后,不管是在block块内拜访变量,还是在block外拜访变量,都是经过 forwarding拜访到堆空间,然后再拜访方针空间内的 变量, 这样就保证了 拜访的变量是同一块内存空间

    Block原理(一)

    Block原理(一)

Block原理(一)

  • Block_byref 持有的变量生命周期完毕,履行 _Block_object_dispose

    • 履行_Block_byref_release函数,依据Block_byref 首地址偏移 找到 Block_byref_2首地址,持续偏移8字节 得到 byref_destroy 履行析构 收回堆内存空间
  • Block_layout 作用域完毕 或许 生命周期完毕, 履行 _Block_release

    • 依据 Block_layout 首地址偏移 找到 Block_descriptor_2 首地址,持续偏移8字节,的到 dispose 履行析构 收回 堆上拓荒的 Block_layout 堆内存空间

读取寄存器检查

Block原理(一)

符号断点 _Block_copy

Block原理(一)

_Block_copy 履行之前,寄存器rax接纳参数 (arm64 读取寄存器x1)

Block原理(一)

履行完之后,ret回来,rax寄存器存储回来值

Block原理(一)

  • 变量a 改为 __block润饰

Block原理(一)

因为__block润饰,Block_layout 中就呈现了 copy 函数地址,经过copy,就履行 _Block_copy

而没有__block润饰,没有 copy dispose 函数,默许履行 _Block_copy

形成这个不同就在于 结构传参时分 flag的差异,__block润饰之前 是0,__block润饰之后, 1 << 25

Block原理(一)

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。