美文网首页
iOS block底层分析(2)--源码探索

iOS block底层分析(2)--源码探索

作者: 冼同学 | 来源:发表于2021-10-15 17:47 被阅读0次

    前言

    IOS开发中大家对block用的非常多,一般情况下仅仅停留在会用的层面,具体的block的底层是如何实现的是一概不知。也许很多人就问block会用不就行了,知道底层原理干什么。比如block是如何捕获变量的,block底层是什么类型。如果你了解它的底层原理就不会有这些疑问。作为一名程序开发者,必须要弄清楚其底层原理。

    准备工作

    1. block底层分析

    1.1 block捕获普通变量(非__block修饰)

    1.1.1 block捕获对象类型

    创建一个NSObject对象在block内部使用。代码如下

    int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        NSObject * obj  = [NSObject alloc];
        void (^ block)(void) = ^{
            NSLog(@"----%@",obj);
        };
        block();
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    通过xcrunmain.m文件编译成main.cpp文件,提取block相关的关键底层代码如下:

    main.cpp提取后代码
    block底层是一个结构体,在__main_block_impl_0结构提中又嵌套了两个结构体__block_impl__main_block_desc_0
    • __block_impl数据结构
    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    

    __block_impl结构体中的FuncPtr用来保存任务功能函数即__main_block_func_0函数的地址。

    • __main_block_desc_0 数据结构
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), 
    __main_block_copy_0, __main_block_dispose_0};
    

    __main_block_desc_0结构中的变量copydispose是非常重要的函数保存着__main_block_copy_0__main_block_dispose_0函数地址。在block进行拷贝释放时调用。

    • __main_block_impl_0构造函数
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    

    构造函数__main_block_impl_0block结构体中相关属性进行设置。构造函数__main_block_impl_0的第一个参数为__main_block_func_0方法实现地址,在声明定义block时,将block的任务函数封装到FuncPtr属性中。

    func定位
    调用block执行时,实际调用的是block->FuncPtr,并将block结构体作为参数传入到方法实现中,底层代码如下:
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    
    1.1.2 block捕获非对象类型

    int类型的变量进行实例探究。代码如下:

    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            int a = 10;
            void (^ block)(void) = ^{
                NSLog(@"----%d",a);
            };
            block();
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    通过xcrunmain.m文件编译成main.cpp文件,提取block相关的关键底层代码如下:


    捕获非对象类型比如基本数据类型,__main_block_desc_0结构中是没有变量copydispose,这就意味着没有copydispose功能。这一点很重要,会影响block的三层拷贝
    1.1.3 总结:main函数中和block操作相关的有两个步骤
    • block的结构体进行初始化赋值,参数有__main_block_func_0任务功能函数,&__main_block_desc_0_DATA描述信息的函数地址以及外界的变量obj

      • block底层会被编译成一个结构体类型
      • block结构体此时自动生成了一个NSObject *__strong obj的变量,对结构体进行赋值时obj(_obj)通过C++方式给block结构体的obj赋值
      • __main_block_func_0赋值给block结构体中的变量__block_impl中的FuncPtr保存
      • &__main_block_desc_0_DATA赋值给block结构体中的变量__main_block_desc_0中的Desc保存
    • 调用blcok结构体中__block_impl变量中的FuncPtr变量执行任务功能函数

      • FuncPtr执行时会把blcok作为参数,因为要获取blcok中的objc变量
      • __main_block_func_0方法中使用的变量其实就是获取的blcok结构体中新生成的obj。结构体中的obj和外界变量的obj指向的同一片内存空间
    • blcok的赋值和执行都是通过blcok内部的数据或者函数去执行,都是用blcok中保存的数据

    1.1.4 问题补充

    为什么普通的变量(不用__block修饰)在blcok内部不能进行修改?
    因为blcok内部的变量obj和外界变量obj指向的是同一块内存,如果此时blcok内部的变量obj重新指向一块内存即obj地址发生改变,而此时外界的变量obj还是指向它开始指向的内存空间没有改变,此时编译器不知道该用哪个obj造成代码有歧义,所以不能进行修改仅仅可读。这也就大家常说的值拷贝

    1.2 block捕获__block修饰的变量

    1.2.1 block捕获__block修饰的对象类型

    给变量obj添加__block修饰,并且在block内存进行修改。代码如下:

     int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            __block NSObject * obj  = [NSObject alloc];
            void (^ block)(void) = ^{
                obj = [NSObject alloc];
                NSLog(@"----%@",obj);
            };
            block();
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    通过xcrunmain.m文件编译成main.cpp文件,提取block相关的关键底层代码如下:

    main.cpp
    main.cpp中可以看出blcok捕获和调用流程和捕获非__block修饰的变量的流程基本一致,区别就是捕获变量的数据结构类型发生了改变

    编译器会把__block修饰的变量底层编译成一个结构体__Block_byref_obj_0,结构如下:

    struct __Block_byref_obj_0 {
      void *__isa;
    __Block_byref_obj_0 *__forwarding;//__Block_byref_obj_0的地址默认赋值时指向自己
     int __flags;//标识
     int __size;//大小
     void (*__Block_byref_id_object_copy)(void*, void*);//copy方法
     void (*__Block_byref_id_object_dispose)(void*);//dispose方法
     NSObject *__strong obj;//对象的地址
    };
    

    编译器把__block修饰的变量底层编译成结构体时,会进行初始化赋值,具体如下:

    • (__Block_byref_obj_0 *)&obj赋值给 __forwarding,其实就是__forwarding指向__Block_byref_obj_0地址。简单的说__Block_byref_obj_0中的__forwarding指向__Block_byref_obj_0
    • __Block_byref_id_object_copy_131赋值给__Block_byref_id_object_copy
    • __Block_byref_id_object_dispose_131赋值给__Block_byref_id_object_dispose
    • 对象的地址赋值给变量 NSObject *__strong obj

    blcok的结构体初始化赋值时,把变量的结构体__Block_byref_obj_0 obj地址作为参数

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_obj_0 *obj; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, 
      __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    

    此时把_obj->__forwarding赋值 __main_block_impl_0结构体中自动生成的变量__Block_byref_obj_0 *obj,也就意味着obj也是指向__Block_byref_obj_0结构体
    (block)->FuncPtr(block)调用任务功能函数代码如下:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    
      __Block_byref_obj_0 *obj = __cself->obj; // bound by ref
      
     (obj->__forwarding->obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)
     ((id)objc_getClass("NSObject"), sel_registerName("alloc"));
     
     NSLog((NSString *)&__NSConstantStringImpl,(obj->__forwarding->obj)); 
    }
    
    • __Block_byref_obj_0 *obj = __cself- >objblcok结构体中的obj赋值给obj,也就是把__Block_byref_obj_0的地址赋值给了__Block_byref_obj_0 *obj
    • obj->__forwarding指向的是__Block_byref_obj_0结构体的地址
    • obj->__forwarding->obj就是__Block_byref_obj_0结构体中的NSObject *__strong obj变量
    • obj->__forwarding->obj修改obj时,block内部和外部变量的指向没有改变,只是改变了外部和内部变量里面的obj,实际上访问到的obj就是同一个,不管这个obj有没有被修改。
    1.2.2 block捕获__block修饰的非对象类型

    __block修饰的int类型变量进行实例探究。代码如下:

     int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            __block int a = 10;
            void (^ block)(void) = ^{
                NSLog(@"----%d",a);
            };
            block();
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    通过xcrunmain.m文件编译成main.cpp文件,提取block相关的关键底层代码如下:

    main.cpp
    block捕获__block修饰的对象类型,在__Block_byref_obj_0结构体中没有copydispose。这一点很重要会影响block的三层拷贝
    1.2.3 总结__Block修饰的对象在block内部做了什么
    • __block修饰的变量底层会生成一个__Block_byref_obj_0结构体
    • __Block_byref_obj_0结构体中保存着对象的地址以及__Block_byref_obj_0地址
    • blcok内部外部的变量都是指向的__Block_byref_obj_0地址。而__Block_byref_obj_0中的变量obj发生改变时blcok内部和外部的变量仍然指向__Block_byref_obj_0地址,然后获取obj

    1.3 __blcok修饰变量指示图

    根据.cpp文件中变量的赋值和修改流程所画的图,更好的理解为什么__block修饰的变量可以修改。如下:

    指示图

    1.4 block未捕获变量(全局或静态)

    1.4.1 block未捕获变量
     int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            
            void (^ block)(void) = ^{
            };
            block();
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    通过xcrunmain.m文件编译成main.cpp文件,提取block相关的关键底层代码如下:

    main.cpp
    如果block未捕获变量,那么在block内部不会自动生成相应的变量,__main_block_desc_0结构体中没有copydisponse函数。
    1.4.2 block捕获全局或静态变量
    static int  a  = 100;
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            void (^ block)(void) = ^{
                NSLog(@"----%d",a);
            };
            block();
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    通过xcrunmain.m文件编译成main.cpp文件,提取block相关的关键底层代码如下:

    main.cpp
    block捕获全局和静态变量和未捕获变量是一样的,此时只是使用了全局或静态变量直接传入。

    2. block底层探究

    通过对main.cpp文件分析,大致理清楚了blcok的变量赋值以及block的调用。但是栈区blcok变成堆区blcok过程还不了解。下面通过汇编跟踪流程的方式进行探究,请继续往下走咯!

    2.1 全局block的底层探究

    运行一个案例给blcok的设置断点,如下图所示:

    查看断点
    block虽然没有值但是block中的变量和底层main.cpp文件中的block结构很相似。下面进行汇编调试:
    汇编断点
    汇编中显示跳转到objc_retainBlock,直接给objc_retainBlock下符号断点。继续调试,如下:
    objc_retainBlock
    • objc_retainBlock汇编中下一步会跳转到_Block_copy
    • objc_retainBlock方法是在libobjc.A.dylib源码库中
    • 真机情况下x0表示方法的第一个参数即消息的接收者。通过lldb调试发现此时的消息接收者是block。而且此时的block是一个全局block

    _Block_copy添加符号断点,继续调试,如下:

    _Block_copy
    • _Block_copy方法在libsystem_blocks.dylib源码库中
    • 通过lldb调试发现_Block_copy的消息接收者是block。此时的block仍然是一个全局block

    _Block_copy汇编结束的位置会return一个返回值,在真机情况下,返回值会保存到x0寄存器中。那么查看返回值如下:

    查看返回值类型
    全局block经过_Block_copy以后什么也没做,直接返回全局block

    2.2 堆区block的底层探究

    创建一个堆block,给blcok的设置断点,如下图所示:

    断点堆block
    打开汇编断点,如下:
    打开汇编断点
    同理添加_Block_copy的符号断点,如下:
    _Block_copy
    _Block_copy方法中进行了lldb调试,发现此时的block栈block,不是堆block,这是为什么呢?继续往下走,查看return的类型,如下:
    查看return的类型
    经过上面的流程已经流程对应的lldb调试得出以下几点:
    • 栈区block经过_Block_copy以后变成堆区block
    • 栈区block和堆区block的地址不一样说明是在堆区新开辟的内存
    • 堆区block里面的变量invokecpoydisponse地址是一样的,说明栈区block在运行时会cpoy一份到堆区,形成一个新的堆区block然后返回

    2.3 栈区block的底层探究

    创建一个栈区的block案例,给栈区blcok设置断点,如下图所示:

    栈block
    同样打开汇编断点,查看里面汇编的情况,如下:
    打开汇编断点
    根据汇编流程,发现栈区block并不会调用objc_retainBlock方法,也就不会调用_Block_copy方法。

    2.4 总结以上三种情况

    • 全局block在运行时调用_Block_copy方法后,仍然是全局block
    • 堆区block是由编译时的栈区block在运行时调用_Block_copy方法,生成新的堆区block
    • 栈区blcok不会进行_Block_copy的操作

    结论:如果block赋值给强引用或者copy修饰的变量,那么block会进行_Block_copy操作,如果是赋值给__weak修饰的变量则不会进行_Block_copy的操作

    2.5 block的类型Block_layout结构体

    通过.cpp文件和汇编对block的类型有个初步了解,但是还是不全面而且不够深入。同类型的blcok里面的变量也是有区别的, 只有通过底层源码去探究block类型。objc_retainBlock方法是在libobjc.A.dylib源码库中,在objc4-818.2源码中全局搜索objc_retainBlock,如下:

    objc_retainBlock
    objc_retainBlock方法中调用了_Block_copy方法和汇编流程是相吻合的。在汇编流程中得知_Block_copy方法在libsystem_blocks.dylib源码库中,但是该源码库并没有开源。经过开发者的不断探索在libclosure-79源码库中找到了_Block_copy方法实现,如下:
    _Block_copy
    _Block_copy方法中发现block的底层是一个Block_layout结构体。那么查看Block_layout结构体如下:
    struct Block_layout {
        //block 的类型
        void * __ptrauth_objc_isa_pointer isa;  // 8 字节
        //用来标识`blcok`的信息是按位存储的,类似于对象中isa的`bits`
        volatile int32_t flags;// 4字节
        //保留字段
        int32_t reserved;// 4字节
        //函数指针,保存任务函数的实现地址 就是.cpp文件中的FuncPtr
        BlockInvokeFunction invoke; // 8字节
        //描述信息
        struct Block_descriptor_1 *descriptor;// 8字节
    };
    

    Block_layout结构体变量的含义:

    • isa:表示block的类型(栈、堆 、全局
    • flags:标识符类似于对象中isabits
    • reserved:保留字段
    • invoke:函数指针,保存任务函数的实现地址 就是.cpp文件中的FuncPtr函数
    • descriptor:描述信息

    flag标识

    // Values for Block_layout->flags to describe block objects
    enum {
        BLOCK_DEALLOCATING =      (0x0001),  // runtime
        BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
        BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
    
    #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
        BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler
    #endif
    
        BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
        BLOCK_NEEDS_FREE =        (1 << 24), // runtime
        BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
        BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
        BLOCK_IS_GC =             (1 << 27), // runtime
        BLOCK_IS_GLOBAL =         (1 << 28), // compiler
        BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
        BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
        BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
    };
    
    • 1BLOCK_DEALLOCATING : 释放标记,-般常用 BLOCK_NEEDS_FREE 做位与操作,一同传入Flags,告知该block可释放
    • 16BLOCK_REFCOUNT_MASK:存储引用计数的值,是一个可选用参数
    • 22BLOCK_SMALL_DESCRIPTOR:包含了Block_descriptor_1Block_descriptor_2Block_descriptor_3
    • 24BLOCK_NEEDS_FREE:低16是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的值
    • 25BLOCK_HAS_COPY_DISPOSE:是否拥有拷贝辅助函数(a copy helper function)
    • 26BLOCK_HAS_CTOR:是否拥有block 析构函数
    • 27BLOCK_IS_GC:标志是否有垃圾回收;//OS X
    • 28BLOCK_IS_GLOBAL:标志是否是全局block
    • 30BLOCK_HAS_SIGNATURE:与BLOCK_USE_STRET相对,判断当前block是否拥有一个签名。用于runtime时动态调用

    在这些标记位中BLOCK_HAS_COPY_DISPOSEBLOCK_HAS_SIGNATURE这两个标记位特别重要

    • BLOCK_HAS_COPY_DISPOSE表示是否有Block_descriptor_2
    • BLOCK_HAS_SIGNATURE表示是否有Block_descriptor_3
    • 因为Block_descriptor_1是必须要有的,所以没有其标记位
    • 通过标记位可以判断Block_descriptor_1是必须有的,Block_descriptor_2BLOCK_DESCRIPTOR_3是可选的

    descriptor描述信息

    • Block_descriptor_1
    #define BLOCK_DESCRIPTOR_1 1
    struct Block_descriptor_1 {
        uintptr_t reserved;     //8字节
        uintptr_t size;         //8字节
    };
    
    • Block_descriptor_2
    #define BLOCK_DESCRIPTOR_2 1
    struct Block_descriptor_2 {
        // requires BLOCK_HAS_COPY_DISPOSE
        BlockCopyFunction copy;          //8字节
        BlockDisposeFunction dispose;    //8字节
    };
    
    • Block_descriptor_3
    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
        // requires BLOCK_HAS_SIGNATURE
        const char *signature; //8字节
        const char *layout;    //8字节  // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    };
    
    • descriptor三种描述信息的总结
      • Block_descriptor_1是结构体类型,其中reserved表示保留信息,size表示block大小
      • Block_descriptor_2是结构体类型,其中copy存的是copy函数地址,dispose 存的是dispose函数地址
      • Block_descriptor_3是结构体类型,其中signature表示签名信息,layout表示block的扩展布局

    descriptor的构造函数探究
    下面探究下descriptor的构造函数,究竟是如何获取descriptor的。

    • Block_descriptor_1
    struct Block_descriptor_1 *desc1 = layout->descriptor;
    
    • Block_descriptor_2
    static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
    {
        uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
        desc += sizeof(struct Block_descriptor_1); //地址偏移获取到descriptor_2
        return (struct Block_descriptor_2 *)desc;
    }
    
    • Block_descriptor_3
    static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
    {
        uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);//descriptor_1的地址
        desc += sizeof(struct Block_descriptor_1);//地址偏移descriptor_1的大小
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {//如果descriptor_2存在
            desc += sizeof(struct Block_descriptor_2);//继续地址偏移descriptor_2大小
        }
        return (struct Block_descriptor_3 *)desc; //得到descriptor_3的地址
    }
    

    Block_descriptor_1是直接赋值,而Block_descriptor_2Block_descriptor_3是通过地址偏移获取的。在结合上面的标志位再次验证Block_descriptor_1是必有的默认的,Block_descriptor_2Block_descriptor_3是可选的,根据标记位判断。

    2.6 lldb验证Block_layout中变量

    2.6.1 堆区block调试验证
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            NSObject * obj  = [NSObject alloc];
            void (^ block)(void) = ^{
                NSLog(@"----%@",obj);
            };
            block();
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    _Block_copy汇编开始的位置或者ret位置进行调试,如下:

    lldb调试
    • flags的值是0x00000000c3000002descriptor的值是0x0000000100390078
    • 打印descriptor中存储的信息得到size的值0x0000000000000028换算成10进制等于40Block_layout结构体的大小。大家可能有疑问Block_layout结构体的大小不是32,因为捕获变量会在block内部生成一个新的变量,现在捕获的是一个指针类型(8字节)所以Block_layout大小总共40个字节
    • BLOCK_HAS_COPY_DISPOSE=(1 << 25)flags & BLOCK_HAS_COPY_DISPOSE用来判断是否有Block_descriptor_2
    • BLOCK_HAS_SIGNATURE=(1 << 30)flags & BLOCK_HAS_SIGNATURE用来判断是否有Block_descriptor_3
    • signature的值是0x000000010038ffa6,打印出的值是v8@?0

    签名信息的补充

    验证签名信息
    v8@?0:v表示返回值为空(没返回值),8表示参数的总大小,@?表示block0表示从0号字节开始
    2.6.2 全局block调试验证
    int main(int argc, char * argv[]) {
        NSString * appDelegateClassName;
        @autoreleasepool {
            void (^ block)(void) = ^{
               
            };
            block();
        }
        return UIApplicationMain(argc, argv, nil, appDelegateClassName);
    }
    

    _Block_copy汇编开始的位置或者ret位置进行调试,如下:

    lldb汇编调试
    • Block_layout结构体大小是0x0000000000000020转换成10进制32,因为没有捕获变量所以大小只有32字节
    • 全局block没有Block_descriptor_2,有Block_descriptor_3
    • 因为内存是连续的Block_descriptor_2没有,那么0x0000000104a03f9b就是Block_descriptor_3起始位置

    2.7 blcok的三层拷贝

    2.7.1 _Block_copy源码探究
    void *_Block_copy(const void *arg) {
        struct Block_layout *aBlock;
    
        if (!arg) return NULL;
        
        // The following would be better done as a switch statement
        aBlock = (struct Block_layout *)arg;
        //block是否需要释放
        if (aBlock->flags & BLOCK_NEEDS_FREE) {
            // latches on high
            latching_incr_int(&aBlock->flags);
            return aBlock;
        }
        //如果是全局block就直接返回
        else if (aBlock->flags & BLOCK_IS_GLOBAL) {
            return aBlock;
        }
        else {// 栈 - 堆 (编译期)
              //编译时期不能生成堆block 只能是栈block 只能进行通过_Block_copy进行开辟堆block
              // Its a stack block.  Make a copy.
            size_t size = Block_size(aBlock);
            struct Block_layout *result = (struct Block_layout *)malloc(size);
            if (!result) return NULL;
            //将aBlock拷贝到result中
            memmove(result, aBlock, size); // bitcopy first
    #if __has_feature(ptrauth_calls)
            // Resign the invoke pointer as it uses address authentication.
            result->invoke = aBlock->invoke;
    
    #if __has_feature(ptrauth_signed_block_descriptors)
            //BLOCK_SMALL_DESCRIPTOR 是包含了Block_descriptor_1、Block_descriptor_2 和 Block_descriptor_3
            //根据flags的标志位来判断的
            if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
                //里面是descriptor的拷贝赋值
                uintptr_t oldDesc = ptrauth_blend_discriminator(
                        &aBlock->descriptor,
                        _Block_descriptor_ptrauth_discriminator);
                uintptr_t newDesc = ptrauth_blend_discriminator(
                        &result->descriptor,
                        _Block_descriptor_ptrauth_discriminator);
    
                result->descriptor =
                        ptrauth_auth_and_resign(aBlock->descriptor,
                                                ptrauth_key_asda, oldDesc,
                                                ptrauth_key_asda, newDesc);
            }
    #endif
    #endif
            // reset refcount
            result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
            result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
            //result:是在堆区创建的block  aBlock:外面传进来的栈的
            //调用Block_descriptor_2`copy`方法
            _Block_call_copy_helper(result, aBlock);
            // Set isa last so memory analysis tools see a fully-initialized object.
            //将isa设置位_NSConcreteMallocBlock即堆block
            result->isa = _NSConcreteMallocBlock;
            return result;
        }
    }
    

    源码的流程如下:

    • 如果block需要释放,则直接释放
    • 如果block是全局block,则不需要操作直接返回
    • 因为堆block需要在堆区申请开辟内存,编译时并不会生成堆block,所以只能是栈block
    • 通过malloc开辟新的内存
    • 通过memmove将栈区block数据拷贝到新开辟的内存中
    • 通过_Block_call_copy_helper调用Block_descriptor_2中的copy方法
    • 将堆上block的isa设置为_NSConcreteMallocBlock

    _Block_call_copy_helper源码探究

     static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
    {   // 获取`copy`函数的函数指针
        if (auto *pFn = _Block_get_copy_function(aBlock))
           // 调用copy方法
            pFn(result, aBlock);
    }
    
    • 通过_Block_get_copy_function方法获取copy函数的函数指针
    • 然后调用copy函数

    _Block_get_copy_function方法探究

    //获取方法的指针
    #define _Block_get_relative_function_pointer(field, type)      \
        ((type)((uintptr_t)(intptr_t)(field) + (uintptr_t)&(field)))
        
    #define _Block_get_function_pointer(field)      \
        (field)
    
    static inline __typeof__(void (*)(void *, const void *))
    _Block_get_copy_function(struct Block_layout *aBlock)
    {
        //如果没有description_2,就没有copy方法,直接返回NULL
        if (!(aBlock->flags & BLOCK_HAS_COPY_DISPOSE))
            return NULL;
        //获取Block_descriptor_1的首地址
        void *desc = _Block_get_descriptor(aBlock);
    #if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
        //如果Block_descriptor_1,Block_descriptor_2,Block_descriptor_3 全都有
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
            struct Block_descriptor_small *bds =
                    (struct Block_descriptor_small *)desc;
            //_Block_get_relative_function_pointer是一个宏
            return _Block_get_relative_function_pointer(
                    bds->copy, void (*)(void *, const void *));
        }
    #endif
    
        struct Block_descriptor_2 *bd2 =
                (struct Block_descriptor_2 *)((unsigned char *)desc +
                                              sizeof(struct Block_descriptor_1));
        //_Block_get_copy_fn 获取copy的函数指针
        return _Block_get_copy_fn(bd2);
    }
    
    • 判断是否有Block_descriptor_2,如果没有返回NULL
    • 如果有Block_descriptor_2,根据不同的情况下去获取copy函数的函数指针

    ** 结论:_Block_call_copy_helper方法的作用就是获取copy函数指针,调用copy函数**
    ** 注意:此时的copy函数是Block_descriptor_2中的copy函数**

    2.7.2 _Block_object_assign源码探究

    main.cpp文件中block结构体初始化时,结构体中的descriptor是通过外面传进来参数进行赋值的,如下所示:

    main.cpp
    图中显示block结构体中的copy变量存储的就是__main_block_copy_0函数的地址,调用block结构体中的copy就是调用__main_block_copy_0函数,方法如下:
    static void __main_block_copy_0(struct __main_block_impl_0*dst,
    struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    • __main_block_copy_0函数中有两个参数dstsrcdst堆区blocksrc栈区block
    • 在探究_Block_copy方法时_Block_call_copy_helper(result, aBlock)方法中调用了copy方法,result堆区blockaBlock栈区block
    • __main_block_copy_0函数中调用了_Block_object_assign方法。
    • _Block_object_assign有三个参数,前两个参数是block捕获的变量,第三个参数捕获变量的类型

    捕获变量的类型是枚举的,如下:

    enum {
        // see function implementation for a more complete description of these fields and combinations
        BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
        BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
        BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
        BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
        BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
    };
    

    常用的类型

    • BLOCK_FIELD_IS_OBJECT,变量类型是普通对象
    • BLOCK_FIELD_IS_BLOCK,变量类型是block类型
    • BLOCK_FIELD_IS_BYREF,变量类型是__block修饰变量

    libclosure-79源码中全局搜索_Block_object_assign,找到其源码如下:

     void _Block_object_assign(void *destArg, const void *object, const int flags) {
        const void **dest = (const void **)destArg;
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_OBJECT:
            // _Block_retain_object_default = fn (arc)
            _Block_retain_object(object);
            *dest = object;
            break;
    
          case BLOCK_FIELD_IS_BLOCK:
            *dest = _Block_copy(object);
            break;
        
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
            *dest = _Block_byref_copy(object);
            break;
            
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK: 
            *dest = object;
            break;
    
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            *dest = object;
            break;
    
          default:
            break;
        }
    }
    
    • 如果变量类型是普通对象,交给系统处理,此时进行指针拷贝*dest = object,引用计数+1
    • 如果变量类型是block类型,进行_Block_copy操作
    • 如果变量类型是__block修饰变量,进行_Block_byref_copy操作
    2.7.3 _Block_byref_copy源码探究

    ** Block_byref结构体探究**
    _Block_byref_copy主要是对变量拷贝_block修饰的变量底层编译成Block_byref类型,其结构如下:

    / Values for Block_byref->flags to describe __block variables
    enum {
        // Byref refcount must use the same bits as Block_layout's refcount.
        // BLOCK_DEALLOCATING =      (0x0001),  // runtime
        // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    
        BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
        BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
        BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
        BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
        BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
        BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler
    
        BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime
    
        BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
        BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime
    };
    
    // __block  -> {}
    
    // 结构体
    struct Block_byref {
        void * __ptrauth_objc_isa_pointer isa; // 8
        struct Block_byref *forwarding;  // 8
        volatile int32_t flags; // contains ref count//4
        uint32_t size; // 4
    };
    
    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
    };
    
    struct Block_byref_3 {
        // requires BLOCK_BYREF_LAYOUT_EXTENDED
        const char *layout;
    };
    
    • __block修饰的变量底层被编译成了Block_byref结构体类型
    • Block_byref的类型和Block_layoutdescriptor比较类似,都是通过flags来判断Block_byref_2Block_byref_3是否存在。既Block_byref_2Block_byref_3可选的。

    _Block_byref_copy方法探究

    static struct Block_byref *_Block_byref_copy(const void *arg) {
        struct Block_byref *src = (struct Block_byref *)arg;
    
        // __block 内存是一样 同一个家伙
        //
        if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            // src points to stack
            // 在堆区开辟内存将外界变量拷贝一份到堆区
            struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
            copy->isa = NULL;
            // byref value 4 is logical refcount of 2: one for caller, one for stack
            copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
            // 堆区中新开辟的copy和 外界变量src的forwarding指向同一片内存
            // 也就意味着它们持有着同一个对象
            copy->forwarding = copy; // patch heap copy to point to itself
            src->forwarding = copy;  // patch stack to point to heap copy
            copy->size = src->size;
            //这里和_Block_copy比较类似
            if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                // Trust copy helper to copy everything of interest
                // If more than one field shows up in a byref block this is wrong XXX
                struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
                struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
                copy2->byref_keep = src2->byref_keep;
                copy2->byref_destroy = src2->byref_destroy;
    
                if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                    struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                    struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                    copy3->layout = src3->layout;
                }
    
                // 捕获到了外界的变量 - 内存处理 - 生命周期的保存
                //调用 Block_byref_2 中的byref_keep方法
                (*src2->byref_keep)(copy, src);
            }
            else {
                // Bitwise copy.
                // This copy includes Block_byref_3, if any.
                memmove(copy+1, src+1, src->size - sizeof(*src));
            }
        }
        // already copied to heap
        else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
            latching_incr_int(&src->forwarding->flags);
        }
        
        return src->forwarding;
    }
    
    • 通过malloc方法开辟内存将外界变量拷贝一份,存放到堆区新开辟的内存
    • copy->forwarding = copysrc->forwarding = copy表明它们的forwarding指向同一片内存,那么它们就持有同一个对象
    • (*src2->byref_keep)(copy, src)调用Block_byref_2中的byref_keep方法

    总结:_Block_byref_copy__block修饰的变量进行拷贝即对Block_byref进行拷贝

    2.7.4 byref_keep 源码探究

    byref_keep方法在结构体Block_byref_2在底层编译时已经被初始化赋值了。查看main.cpp文件,如下:

    main.cpp
    main.cpp文件显示Block_byref_2结构体中byref_keep存储__Block_byref_id_object_copy_131函数指针,而byref_destroy存储__Block_byref_id_object_dispose_131函数指针 结构体大小统计
    __Block_byref_id_object_copy_131再次调用_Block_object_assign方法,此次_Block_object_assign方法中的参数是Block_byref结构体中的obj对象。所以此次会走BLOCK_FIELD_IS_BLOCK流程
    2.7.5 总结
    • 通过_Block_copy方法,将栈区blcok拷贝一份放在堆区
    • __block修饰的对象,通过_Block_byref_copy方法,将Block_byref结构体类型进行拷贝
    • 通过_Block_object_assign方法,对Block_byref中的对象处理。实际上这一层没有进行拷贝,但是走的还是拷贝的流程

    2.8 _Block_object_dispose探究

    _Block_object_dispose方法和_Block_object_assign方法是对应的,那么我们还是先从汇编入手,如下所示:

    打开汇编断点
    汇编显示在block释放时会调用_Block_object_dispose方法。在libclosure-79源码中全局搜索_Block_object_dispose,如下:
    void _Block_object_dispose(const void *object, const int flags) {
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
            // get rid of the __block data structure held in a Block
            _Block_byref_release(object);
            break;
          case BLOCK_FIELD_IS_BLOCK:
            _Block_release(object);
            break;
          case BLOCK_FIELD_IS_OBJECT:
            _Block_release_object(object);
            break;
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
            break;
          default:
            break;
        }
    }
    

    _Block_object_dispose方法就是调用了block结构体中Block_descriptor_2中的disponse,根据捕获的变量类型_Block_object_dispose进行不同的释放操作。如果是__block修饰的变量会调用_Block_byref_release方法

    _Block_byref_release方法探究

    static void _Block_byref_release(const void *arg) {
        struct Block_byref *byref = (struct Block_byref *)arg;
        
        byref = byref->forwarding;
        
        if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
            int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
            os_assert(refcount);
            if (latching_decr_int_should_deallocate(&byref->flags)) {
                if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                    struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                    //byref_destroy 释放销毁变量 和 byref_keep对应
                    (*byref2->byref_destroy)(byref);
                }
                //释放
                free(byref);
            }
        }
    }
    

    _Block_byref_release方法就是对象变量释放销毁

    3. 总结

    block的底层探索过程非常的绕,需要满满的去理清内部的关系。

    相关文章

      网友评论

          本文标题:iOS block底层分析(2)--源码探索

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