美文网首页iOSIOS新手进阶
iOS底层探索之Block(五)——Block源码分析(__bl

iOS底层探索之Block(五)——Block源码分析(__bl

作者: 俊而不逊 | 来源:发表于2021-09-02 13:53 被阅读0次

    回顾

    在上一篇博客中,通过对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 的三层拷贝

    更多内容持续更新

    🌹 喜欢就点个赞吧👍🌹

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

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

    相关文章

      网友评论

        本文标题:iOS底层探索之Block(五)——Block源码分析(__bl

        本文链接:https://www.haomeiwen.com/subject/ouaqwltx.html