iOS底层探索之Block(五)——Block源码分析(__block 底层都做了什么?)

50 篇文章 6 订阅
5 篇文章 2 订阅

回顾

在上一篇博客中,通过对block追根溯源,汇编跟踪调式,源码分析,对底层结构和 block的属性方法都有一定的认识, 那么本篇博客将继续对block的底层进行分析。

Block探索分析
iOS底层探索之Block(一)——初识Block(你知道几种Block呢?)

iOS底层探索之Block(二)——如何解决Block循环引用问题?

iOS底层探索之Block(三)——Block的本质

iOS底层探索之Block(四)——Block的探索和源码分析

1. block底层探索

block的结构和签名都分析完了,但是block最难的点还是怎么捕获到变量等。

cpp查看底层结构

再去瞄一眼底层结构,如下代码:

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

	NSObject *objc = [NSObject alloc];
    void (^jp_block)(void) = ^{
        NSLog(@"zjpreno: %@ ",objc);
    };
    jp_block(); 
}

@end

通过 xcrun -sdk iphonesimulator clang -S -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.2 ViewController.m 命令生成.cpp文件
ViewController.cpp
通过生成的.cpp文件可以看到

  • __ViewController__viewDidLoad_block_copy_0是对应Block_descriptor_2里面的 copy
  • __ViewController__viewDidLoad_block_dispose_0是对应Block_descriptor_2里面的 dispose
  • __ViewController__viewDidLoad_block_copy_0方法里面怎么还多了_Block_object_assign这个玩意, __ViewController__viewDidLoad_block_dispose_0里面多了_Block_object_dispose,让人百思不得其解,先别急请继续往下看👇。

底层结构分析

cpp对应源码中的Block_descriptor信息
这个 cpp的结构体即对应源码中的Block_descriptor信息。

  • reservedsize对应Block_descriptor_1的两个属性。
  • void (*copy)void (*dispose)对应Block_descriptor_2的两个方法。
  • copy方法的实现中,会调用_Block_object_assign,此过程即为外部变量的捕获的方法。
  • dispose方法的实现中,会调用_Block_object_dispose,此过程为释放方法。

2. block源码分析

在源码中搜索_Block_object_assign,找到如下注释信息:
_Block_object_assign注释信息
Block 被复制到堆时,一个Block 可以引用四种不同的需要帮助的东西。

  1. 基于C++堆栈的对象
  2. Objective-C 对象的引用
  3. 其他块
  4. __block 变量

在这些情况下,辅助函数由编译器合成,用于 Block_copyBlock_release,称为复制处置辅助函数。 复制助手为基于 C++堆栈的对象发出对C++ const 复制构造函数的调用,并为其余调用运行时支持函数_Block_object_assigndispose helper调用 C++析构函数用于情况 1,调用 _Block_object_dispose用于其余情况。

  • 编译器在生成复制/处置助手时使用的运行时支持函数
  • _Block_object_assign()_Block_object_dispose() 参数的值

parameters

_Block_object_assign_Block_object_disposeflags 参数设置为:

  • BLOCK_FIELD_IS_OBJECT (3),对于Objective-C Object的情况
  • BLOCK_FIELD_IS_BLOCK (7),对于另一个 block 的情况
  • BLOCK_FIELD_IS_BYREF (8),对于__block变量的情况。

如果 __block 变量被标记为weak,则编译器也在 BLOCK_FIELD_IS_WEAK (16)

所以 Block copy/dispose helper 应该只生成 37824 四个标志值。

上面是源码的注释,那么现在去验证一下:

验证
cpp我们也可以看到对于 OC 对象,是 BLOCK_FIELD_IS_OBJECT (3),那么我们现在去加一下__block,看看会不会变成 BLOCK_FIELD_IS_BYREF (8)呢?
__block验证
看到没有,加了__block之后就变成 BLOCK_FIELD_IS_BYREF (8),还有谁,45 度仰望天花板,我这该死无处安放的魅力啊!
666

_Block_object_assign

  • _Block_object_assign

对比分析

  • 如果持有变量是BLOCK_FIELD_IS_OBJECT类型,即没有__block修饰,dest指针指向objec,引用计数加1,原本的对象地址给了block里面的目标对象,这样就都指向同一个地址空间
  • 如果是BLOCK_FIELD_IS_BLOCK类型,也就是block的类型,就是捕获到了 block则进行_Block_copy操作,把objec的内容复制一份到自己的里面,也就是拷贝到堆区
  • 如果是BLOCK_FIELD_IS_BYREF,即有__block修饰,则会调用_Block_byref_copy

_Block_byref_copy

  • _Block_byref_copy

_Block_byref_copy

  • 将外部对象封装成结构体Block_byref *src

  • 如果是有引用计数的正常对象,就对引用计数进行处理,调用malloc,生成一个Block_byref *copy,如果不是,则会调用memmove

  • 设置forwarding,保证block内部和外部都指向同一个对象
    forwarding

Block_byref_2

  • Block_byref_2
    Block_byref_2
    这里就是对 block捕获的变量进行处理了,调用了byref_keep对其生命周期进行保存。Block_byref的设计思路和Block_layoutdescriptor流程类似,通过byref->flag标识码判断对应的属性,以此来判断Block_byref_2是否存在,如下图所示:
    Block_byref
    如果我们的block捕获了使用__block修饰了外部变量,在cpp文件中,Block_byref结构体中就会默认生成两个方法,即对应Block_byref_2keep方法和destory方法,验证如下:
    cpp 验证Block_byref_2

cpp文件中这两个函数的实现如下:

keep方法和destory实现

  • _Block_object_assign
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);

(char*)dst + 40)相当于 objec,指针平移
对比

上面的 40 是怎么来的呢?如下:

内存大小
__Block_byref_id_object_copy_131方法的调用会调用_Block_object_assign函数,对Block_byref结构体中的对象进行BLOCK_FIELD_IS_OBJECT流程处理。

_Block_object_dispose

对于_Block_object_dispose方法,也就是释放流程,也是类似的。
_Block_object_dispose
也是对block类型的判断,再调用_Block_release(object)走释放流程。

_Block_release

  • flags parameter

flags parameter

3. 总结

block 的三层拷贝

  • 如果是__Block修饰的变量,会对block进行copy操作,从栈区拷贝到堆区
  • block捕获变量,对Block_byref结构体的拷贝
  • Block_byref会对传入的 objec进行拷贝,到此完成 block 的三层拷贝

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡卡西Sensei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值