美文网首页
block分析(下)

block分析(下)

作者: 浅墨入画 | 来源:发表于2021-10-15 22:27 被阅读0次

    block通过clang分析

    带着下面的疑问,我们去探索block原理

    • 探索block底层源码
    • block在底层是如何编译的?或者说block在底层会编译成什么样的结构
    • block的内部源码结构又是什么样的?
    • 我们经常说的invoke是在哪里呢?
    • 对象有isa的指向,那么block的isa指向又是什么呢?
    • invoke的签名
    • block捕获的变量会到哪里去?
    • block捕获的对象是如何保存和释放的?
    block通过clang分析

    捕获外界变量

    #include "stdio.h" 
    int main() {
        int a = 8;
        void(^block)(void) = ^{ 
            printf("LG_Cooci - %d",a);
        }; 
        block(); 
        return 0; 
    }
    
    • 通过xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc block.c -o block.cpp 生成.cpp文件,查看.cpp文件中的main函数
    int main() {
        int a = 8;
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    
    // 剔除强转代码,方便阅读
    int main() {
        int a = 8;
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a)); 
        block->FuncPtr(block); 
        return 0; 
    }
    
    1. 调用__main_block_impl_0函数传入三个参数
    2. 调用block的FuncPtr函数并传入block

    参数一参数三

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int a = __cself->a; // bound by copy
      printf("LG_Cooci - %d",a);
    }
    

    参数二

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    • 查看__main_block_impl_0的定义,发现是一个结构体
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int a;
      __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;
      }
    };
    
    1. 捕获外界变量时,在结构体内部,会生成相应的成员变量用来存储
    2. 成员变量a通过结构体的构造函数赋值:a(_a)
    3. main函数中调用Bolck,由于捕获外界变量,此时传入的FuncPtr中的block发挥作用:
      block为自身结构体的指针,将block中的成员变量a赋值给临时变量a,然后对其打印;
      临时变量a__cself->a的值相同,但地址不同;
      由于a是值拷贝,Bolck的代码块中不能对a的值进行改变,会造成编译器的代码歧义。所以此时的a是只读的。
    4. 捕获外界变量并赋值强引用变量,本该是堆区Block,但结构体中impl.isa赋值为&_NSConcreteStackBlock,标记为栈区Block。因为在编译时无法开辟内存空间,所以暂且标记为StackBlock。在运行时会根据情况将Block拷贝到堆区,然后生成MallocBlock

    未捕获外界变量

    #include "stdio.h"
    int main() {
        void(^block)(void) = ^{
            printf("LG");
        };
        block();
        return 0;
    }
    
    • 生成.cpp文件查看main函数
    int main() {
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    
    // 剔除强转代码,方便阅读
    int main() {
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); 
        block->FuncPtr(block); 
        return 0; 
    }
    
    1. 调用__main_block_impl_0函数,传入两个参数,取地址并赋值block
    2. 缺少的第三个参数为外界变量a

    参数一

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("LG");
    }
    

    参数二

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    • 查看__main_block_impl_0
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    1. Block的本质是结构体,main函数中调用的是结构体的构造函数
    2. 结构体中包含两个成员变量:
      __block_impl结构体类型的impl
      __main_block_desc_0结构体指针类型的Desc
    3. 构造函数中生成了flags等于0的默认值,赋值impl.Flags
    4. 参数fpBlock代码块函数指针,赋值impl的FuncPtr
    5. 参数desc赋值给成员变量Desc
    6. 所以main函数中,代码block->FuncPtr(block)就是在对Block进行调用

    由此可见当Block仅定义不调用执行,不会触发Block中的代码块

    捕获使用__block修饰的外部变量

    #include "stdio.h" 
    int main() { 
        __block int a = 18;
        void(^block)(void) = ^{ 
            a++;
            printf("LG - %d",a); 
        }; 
        block(); 
        return 0;
    }
    
    • 生成block.cpp文件查看main函数
    int main() {
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18};
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        return 0;
    }
    
    // 剔除强转代码,方便阅读
    int main() { 
        __Block_byref_a_0 a = { 
            0,
            (__Block_byref_a_0 *)&a, 
            0, 
            sizeof(__Block_byref_a_0), 
            18 
        };
        void(*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344); 
        block->FuncPtr(block);
        return 0; 
    }
    

    int类型a对应生成__Block_byref_a_0结构体。成员变量a对结构体a取地址,转为结构体指针

    • 查看__Block_byref_a_0
    struct __Block_byref_a_0 { 
        void *__isa; 
        __Block_byref_a_0 *__forwarding; 
        int __flags;
        int __size; 
        int a; 
    };
    
    1. 其中__forwarding存储的就是a结构体的地址
    2. 最后的成员变量a存储18的值
    3. 结构体中存储了自身的地址和值

    参数一

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 
        __Block_byref_a_0 *a = __cself->a; // bound by ref 
        (a->__forwarding->a)++; 
        printf("LG - %d",(a->__forwarding->a));
    }
    

    参数二

    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};
    
    
    //参数2使用的copy和dispose函数
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
    } 
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        _Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
    }
    
    • 查看__main_block_impl_0
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_a_0 *a; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
    1. 局部变量a__cself->a指针地址相同,它的值一旦改变,相当于对外界变量的值进行修改
    2. 没有__block修饰属于值拷贝,也就是深拷贝。拷贝的值不可更改,它们指向不同的内存空间
    3. 使用__block修饰属于地址拷贝,也就是浅拷贝。生成的对象指向同一片内存空间,内部修改等同于对外界变量的修改

    block汇编分析得到签名copy过程

    Block流程分析
    • ViewController中编写如下代码
    - (void)viewDidLoad {
        [super viewDidLoad];
        void (^block1)(void) = ^{
            NSLog(@"LG_Block");
        };
        block1();
    }
    
    • block定义处添加断点,运行项目查看汇编

      image.png
    • 添加objc_retainBlock符号断点调试,发现来自libobjc.A.dylib框架

      image.png
    1. 打开objc4-818.2源码,查找objc_retainBlock函数

      image.png
    2. objc源码中找不到_Block_copy函数的实现,添加_Block_copy符号断点,发现在libsystem_blocks.dylib库中,但该框架暂未开源,我们可以在libclosure-79替代工程中查看。

      image.png
    Block结构
    • libclosure-79工程中查看,发现Block的真实类型是Block_layout
    image.png
    • 查看Block_layout结构体
      image.png

    isa:标示Block类型的类;
    flags:标识符,按bit位表示Block的附加信息,类似于isa中的位域;
    reserved:预留位置;
    invoke:函数指针,指向Block实现的调用地址;
    descriptor:附加信息,例如:存储保留变量数、Block的大小、进行copy或dispose的函数指针。

    • 查看标识符flags
      image.png
    image.png

    BLOCK_DEALLOCATING:释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该Block可释放;
    BLOCK_REFCOUNT_MASK:存储引用引用计数的值,是一个可选用参数;
    BLOCK_NEEDS_FREE:低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值;
    BLOCK_HAS_COPY_DISPOSE:是否拥有拷贝辅助函数,用于拷贝到堆区,决定block_description_2;
    BLOCK_HAS_CTOR:是否拥有Block的C++析构函数;
    BLOCK_IS_GC:标志是否有垃圾回收,OSX;
    BLOCK_IS_GLOBAL:标志是否是全局Block;
    BLOCK_USE_STRET:与BLOCK_HAS_SIGNATURE相对,判断是否当前Block拥有一个签名,用于runtime时动态调用;
    BLOCK_HAS_SIGNATURE:是否有签名;
    BLOCK_HAS_EXTENDED_LAYOUT:是否有拓展,决定block_description_3。

    运行时Copy
    • 进入_Block_copy函数的汇编代码,通过读取寄存器分析block的数据状态变化
      image.png
    image.png image.png

    发现是一个全局block

    修改代码,让block捕获外部变量
    NSObject *objc1 = [NSObject alloc];
    void (^block1)(void) = ^{
        NSLog(@"LG_Block %@", objc1);
    };
    block1();
    
    image.png
    • 调用objc_retainBlock方法时,此时是一个__NSStackBlock__
    image.png image.png
    • 继续跟踪汇编运行至_Block_copy,很显然block通过该方法完成了内存的变化,如何验证呢?在retab的地方设置断点,也就是在方法return的地方设置断点,查看其最终的处理结果
    image.png image.png image.png

    此时的block__NSMallocBlock__,并且地址发生了改变,从栈区拷贝到了堆区

    • 通过libclosure-79搜索_Block_copy如下
    image.png

    得出结论:捕获了外部变量的block,编译时是栈block,在运行时通过_Block_copy方法会copy到堆区,变成堆block

    上面打印当前Block为MallocBlock,同时还打印出signatureinvokecopydispose等数据

    • 其中signaturecopydispose等数据存储在Block_layout结构体的descriptor
    • invoke:函数的调用者指针
    • signature:当前block的签名
    签名含义

    对于签名v8@?0的解释

    • v:返回值类型viod
    • 8:方法所占用的内存8字节
    • @?:参数0类型
    • 0:参数0的起始位置,从0字节开始

    签名的详细信息,可以使用NSMethodSignaturesignatureWithObjCTypes方法输出

    (lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"] <NSMethodSignature: 0xbb2001894a750adf>
        number of arguments = 1
        frame size = 224
        is special struct return? NO
        return value: -------- -------- -------- --------
            type encoding (v) 'v'
            flags {}
            modifiers {}
            frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
            memory {offset = 0, size = 0}
         argument 0: -------- -------- -------- --------
            type encoding (@) '@?'
            flags {isObject, isBlock}
            modifiers {}
            frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} 
            memory {offset = 0, size = 8}
    
    • number of arguments = 1:表示传入一个参数;
    • is special struct return? NO:没有返回值;
    • return value:返回值:
      type encoding (v) 'v':void类型的缩写;
      memory {offset = 0, size = 0}:无返回值,故此size为0。
    • argument 0:参数0;
    • type encoding (@) '@?':其中@为id类型,?未知类型;
    • flags {isObject, isBlock}:即是Object类型,也是Block类型;
    • memory {offset = 0, size = 8}:参数0从0字节开始,占8字节。

    blocklayout的结构

    libclosure-79工程中查看Block_layout结构体

    struct Block_layout {
        void * __ptrauth_objc_isa_pointer isa;
        volatile int32_t flags; // contains ref count
        int32_t reserved;
        BlockInvokeFunction invoke;
        struct Block_descriptor_1 *descriptor;
        // imported variables
    };
    

    Block_layout中的参数descriptorBlock_descriptor_1类型的结构体
    其中descriptor有三种类型:Block_descriptor_1Block_descriptor_2Block_descriptor_3
    Block_descriptor_1一定存在,Block_descriptor_2Block_descriptor_3为可选

    1. Block_descriptor_1:存储预留字段和Block大小
    #define BLOCK_DESCRIPTOR_1 1
    struct Block_descriptor_1 {
        uintptr_t reserved;
        uintptr_t size;
    };
    
    1. Block_descriptor_2:存储copy和dispose的函数指针
    #define BLOCK_DESCRIPTOR_2 1
    struct Block_descriptor_2 {
        // requires BLOCK_HAS_COPY_DISPOSE
        BlockCopyFunction copy;
        BlockDisposeFunction dispose;
    };
    

    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);
        return (struct Block_descriptor_2 *)desc;
    }
    
    • 通过位与运算,判断Block_descriptor_2不存在,返回NULL
    • 否则读取Block_descriptor_1的首地址,偏移自身大小,即:Block_descriptor_2的首地址
    • 强转为Block_descriptor_2的结构体指针返回。
    1. Block_descriptor_3:存储signature签名和layout
    #define BLOCK_DESCRIPTOR_3 1
    struct Block_descriptor_3 {
        // requires BLOCK_HAS_SIGNATURE
        const char *signature;
        const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
    };
    

    Block_descriptor_3的读取:

    static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
    {
        uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
        desc += sizeof(struct Block_descriptor_1);
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
            desc += sizeof(struct Block_descriptor_2);
        }
        return (struct Block_descriptor_3 *)desc;
    }
    
    • 通过位与运算,判断Block_descriptor_3不存在,返回NULL
    • 否则读取Block_descriptor_1的首地址,偏移自身大小,即:Block_descriptor_2的首地址;
    • 通过位与运算,判断Block_descriptor_2是否存在:
      存在,再偏移Block_descriptor_2大小,即:Block_descriptor_3的首地址
      不存在,强转为Block_descriptor_3的结构体指针返回

    小结:

    • block的Block_descriptor_1相关属性是必然存在,其中reserved为保留字段,size为block的大小
    • Block_descriptor_3是可选的参数,而这里就通过flags字段来判断block是否存在Block_descriptor_3的相关属性
    • Block_descriptor的get方法可以发现,通过地址平移的方式获取对应的值,并且在获取Block_descriptor_3时会判断Block_descriptor_2是否存在,如果不存在,就不需要添加Block_descriptor_2的地址空间
    lldb验证descriptor
    image.png

    block的捕获变量生命周期

    在探索block的捕获变量生命周期之前,这里有几个疑问?

    • block是如何捕获外部变量的? __block捕获变量又是什么呢?
    • block中的copy函数释放函数又是什么呢?
    • block三重拷贝过程是怎样的?

    查看转换后的cpp文件__ViewController__viewDidLoad_block_desc_0结构体的定义

    // @implementation ViewController
    ......
    static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->objc1, (void*)src->objc1, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->objc1, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __ViewController__viewDidLoad_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
      void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
    } __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};
    ......
    // @end
    
    • 该结构体即对应源码中的__ViewController__viewDidLoad_block_desc_0信息,其中reservedsize对应Block_descriptor_1的两个属性
    • 另外void (*copy)void (*dispose)对应Block_descriptor_2的两个方法
    • 在copy方法的实现中,会调用_Block_object_assign,此过程即为外部变量捕获释放过程

    libclosure-79源码中全局搜索_Block_object_assign得到以下注释信息

    The flags parameter of _Block_object_assign and _Block_object_dispose is set to
        * BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
        * BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
        * BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
    If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16)
    
    So the Block copy/dispose helpers should only ever generate the four flag values of 3, 7, 8, and 24.
    

    _Block_object_assign_Block_object_dispose的flags参数设置为:
    BLOCK_FIELD_IS_OBJECT (3),捕获Objective-C Object的情况
    BLOCK_FIELD_IS_BLOCK (7),捕获另一个block的情况
    BLOCK_FIELD_IS_BYREF (8),捕获__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修饰的变量;
    BLOCK_FIELD_IS_WEAK:weak弱引用变量;
    BLOCK_BYREF_CALLER:返回的调用对象,处理block_byref内部对象内存会增加一个额外标记,配合flags一起使用。

    • 查看_Block_object_assign函数
    // 当 block 和 byref 要持有对象时,它们的 copy helper 函数会调用这个函数来完成 assignment 
    // 参数 destAddr 其实是一个二级指针,指向真正的目标指针
    
    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:
            /******* 
            id object = ...; 
            [^{ object; } copy]; 
            ********/
            
            // _Block_retain_object_default = fn (arc) 
            // 默认什么都不干,但在 _Block_use_RR() 中会被 Objc runtime 或者 CoreFoundation 设置 retain 函数 
            // 其中,可能会与 runtime 建立联系,操作对象的引用计数什么的 
            // 可以理解为交给系统 ARC 处理
            _Block_retain_object(object); 
            // 使 dest 指向的目标指针指向 
            object *dest = object; 
            break;
            
          case BLOCK_FIELD_IS_BLOCK: 
            /******* 
            void (^object)(void) = ...; 
            [^{ object; } copy];
            ********/ 
          
            // 使 dest 指向拷贝到堆上object 
            *dest = _Block_copy(object); 
            break;
          
          case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
          case BLOCK_FIELD_IS_BYREF:
            /******* 
              // copy the onstack __block container to the heap 
              // Note this __weak is old GC-weak/MRC-unretained.
              // ARC-style __weak is handled by the copy helper directly. 
              __block ... x; 
              __weak __block ... x; 
              [^{ x; } copy];
             ********/
            
            // 使 dest 指向拷贝到堆上的byref 
            *dest = _Block_byref_copy(object); 
            break;
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT: 
          case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
            /******* 
            // copy the actual field held in the __block container 
            // Note this is MRC unretained __block only. 
            // ARC retained __block is handled by the copy helper directly. 
            __block id object; 
            __block void (^object)(void); 
            [^{ object; } copy]; 
            ********/
            
            // 使 dest 指向的目标指针指向 object 
            *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:
            /******* 
            // copy the actual field held in the __block container
            // Note this __weak is old GC-weak/MRC-unretained. 
            // ARC-style __weak is handled by the copy helper directly. 
            __weak __block id object;
            __weak __block void (^object)(void); 
            [^{ object; } copy];
            ********/
            
            // 使 dest 指向的目标指针指向 object 
            *dest = object;
            break;
            
          default: 
              break; 
          }
      }
    
    1. 普通对象类型,交给系统ARC处理,使dest指向的目标指针指向object
    2. Block类型作为变量,调用_Block_copy函数,使dest指向拷贝到堆上object
    3. 使用__block修饰的变量,调用_Block_byref_copy函数,使dest指向拷贝到堆上的byref
    • 查看_Block_byref_copy函数
    // 1. 如果 byref 原来在堆上,就将其拷贝到堆上,拷贝的包括 Block_byref、Block_byref_2、Block_byref_3 
    // 被 __weak 修饰的 byref 会被修改 isa 为 _NSConcreteWeakBlockVariable
    // 原来 byref 的 forwarding 也会指向堆上的 byref; 
    // 2. 如果 byref 已经在堆上,就只增加一个引用计数。
    static struct Block_byref *_Block_byref_copy(const void *arg) {
        // arg 强转为 Block_byref * 类型 
        struct Block_byref *src = (struct Block_byref *)arg;
        
        // 引用计数等于 0 
        if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
            // src points to stack 
            // 为新的 byref 在堆中分配内存 
            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 
            // 新 byref 的 flags 中标记了它是在堆上,且引用计数为 2。 
            // 为什么是 2 呢?注释说的是 non-GC one for caller, one for stack 
            // one for caller 很好理解,那 one for stack 是为什么呢? 
            // 看下面的代码中有一行 src->forwarding = copy。src 的 forwarding 也指向了 copy,相当于引用了 
            copy copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4; 
            
            // 堆上 byref 的 forwarding 指向自己
            copy->forwarding = copy; // patch heap copy to point to itself 
            // 原来栈上的 byref 的 forwarding 现在也指向堆上的 byref 
            src->forwarding = copy; // patch stack to point to heap copy 
            // 拷贝 size 
            copy->size = src->size;
            
            // 如果 src 有 copy/dispose helper 
            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 
                // 取得 src 和 copy 的 Block_byref_2 
                struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1); 
                struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1); 
                
                // copy 的 copy/dispose helper 也与 src 保持一致 
                // 因为是函数指针,估计也不是在栈上,所以不用担心被销毁 
                copy2->byref_keep = src2->byref_keep; 
                copy2->byref_destroy = src2->byref_destroy;
                
                // 如果 src 有扩展布局,也拷贝扩展布局
                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); 
                    
                    // 没有将 layout 字符串拷贝到堆上,是因为它是 const 常量,不在栈上 
                    copy3->layout = src3->layout;
                }
                
                // 调用 copy helper,因为 src 和 copy 的 copy helper 是一样的,所以用谁的都行,调用的都是同一个函数 
                // 发起第三层拷贝 
                (*src2->byref_keep)(copy, src);
            } else { 
                // Bitwise copy. 
                // This copy includes Block_byref_3, if any. 
                // 如果 src 没有 copy/dispose helper 
                // 将 Block_byref 后面的数据都拷贝到 copy 中,一定包括 Block_byref_3 
                memmove(copy+1, src+1, src->size - sizeof(*src)); 
            }
        }
        
        // already copied to heap 
        // src 已经在堆上,就只将引用计数加 1 
        else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) { 
            latching_incr_int(&src->forwarding->flags); 
        }
        
        return src->forwarding;
    }      
    
    1. 外部对象封装成结构体Block_byref *src
    2. 如果是BLOCK_FIELD_IS_BYREF,则会调用malloc生成一个Block_byref *copy
    3. 设置forwarding保证block内部外部都指向同一个对象
    copy->forwarding = copy; 
    src->forwarding = copy;
    
    • Block_byrefkeep函数和destroy处理,并进行byref_keep函数的调用
    image.png

    Block_byref的设计思路和Block_layoutdescriptor流程类似,通过byref->flag标识码判断对应的属性,以此来判断Block_byref_2是否存在

    • 查看Block_byref结构体定义
    // 结构体
    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修饰了外部变量,编译生成的cpp文件中Block_byref结构体中就会默认生成两个方法,即对应Block_byref_2keep方法和destory方法

    image.png

    cpp文件中搜索这两个函数的实现如下图

    image.png

    此过程会再次调用_Block_object_assign函数,对Block_byref结构体中的对象进行BLOCK_FIELD_IS_OBJECT流程处理

    至此block的三重拷贝流程如下

    • _Block_copy函数,负责Block对象的自身拷贝,从栈区拷贝到堆区
    • _Block_byref_copy函数,将Block_byref拷贝到堆区
    • _Block_object_assign函数,将捕获的外界变量拷贝到堆区
    Block释放
    • 查看_Block_release函数
    // block 在堆上,才需要 release,在全局区和栈区都不需要 release. 
    // 先将引用计数减 1,如果引用计数减到了 0,就将 block 销毁 
    void _Block_release(const void *arg) { 
        struct Block_layout *aBlock = (struct Block_layout *)arg; 
        
        // 如果 block == nil 
        if (!aBlock) return; 
        
        // 如果 block 在全局区 
        if (aBlock->flags & BLOCK_IS_GLOBAL) return; 
        
        // block 不在堆上 
        if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return; 
        
        // 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 block 需要被销毁 
        if (latching_decr_int_should_deallocate(&aBlock->flags)) { 
            // 调用 block 的 dispose helper,dispose helper 方法中会做诸如销毁 byref 等操作 
            _Block_call_dispose_helper(aBlock); 
            
            // _Block_destructInstance 啥也不干,函数体是空的 
            _Block_destructInstance(aBlock); free(aBlock); 
        } 
    }
    

    _Block_copy相似,通过_Block_call_dispose_helper函数调用_Block_object_dispose函数

    • 查看_Block_object_dispose函数
    // 当 block 和 byref 要 dispose 对象时,它们的 dispose helper 会调用这个函数 
    void _Block_object_dispose(const void *object, const int flags) { 
        switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
            // 如果是 byref 
            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 
              // 对 byref 对象做 release 操作 
              _Block_byref_release(object); 
              break; 
              
            case BLOCK_FIELD_IS_BLOCK: 
              // 对 block 做 release 操作 
              _Block_release(object); 
              break;
            
            case BLOCK_FIELD_IS_OBJECT: 
              // 默认啥也不干,但在 _Block_use_RR() 中可能会被 Objc runtime 或者 CoreFoundation 设置一个 release 函数,里面可能会涉及到 runtime 的引用计数 
              _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; 
          } 
      }
    
    1. 普通对象类型,交给系统ARC处理
    2. Block类型作为变量,调用_Block_release函数
    3. 使用__block修饰的变量,调用_Block_byref_release函数,对byref对象做release操作
    • 查看_Block_byref_release函数
    // 对 byref 对象做 release 操作, 
    // 堆上的 byref 需要 release,栈上的不需要 release,
    // release 就是引用计数减 1,如果引用计数减到了 0,就将 byref 对象销毁 
    static void _Block_byref_release(const void *arg) { 
        struct Block_byref *byref = (struct Block_byref *)arg;
        
        // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?) 
        // 取得真正指向的 byref,如果 byref 已经被堆拷贝,则取得是堆上的 byref,否则是栈上的,栈上的不需要 release,也没有引用计数
        byref = byref->forwarding; 
        
        // byref 被拷贝到堆上,需要 release 
        if (byref->flags & BLOCK_BYREF_NEEDS_FREE) { 
            // 取得引用计数
            int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK; 
            os_assert(refcount);
            
            // 引用计数减 1,如果引用计数减到了 0,会返回 true,表示 byref 需要被销毁 
            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);
                    
                    // dispose helper 藏在 Block_byref_2 里 
                    (*byref2->byref_destroy)(byref); 
                } 
                
                free(byref);
            }
        } 
    }
    

    _Block_byref_copy相似,由byref_destroy发起对象的release

    • 对应cpp代码
    static void __Block_byref_id_object_dispose_131(void *src) { 
        _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    
    • 调用_Block_object_dispose函数,传入objc实例对象
    • 进入_Block_object_dispose函数,命中普通对象类型的release逻辑

    相关文章

      网友评论

          本文标题:block分析(下)

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