block分析(下)

2021-10-15  本文已影响0人  浅墨入画

block通过clang分析

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

block通过clang分析

捕获外界变量

#include "stdio.h" 
int main() {
    int a = 8;
    void(^block)(void) = ^{ 
        printf("LG_Cooci - %d",a);
    }; 
    block(); 
    return 0; 
}
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)};
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;
}
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)};
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;
}
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取地址,转为结构体指针

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*/);
}
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流程分析
- (void)viewDidLoad {
    [super viewDidLoad];
    void (^block1)(void) = ^{
        NSLog(@"LG_Block");
    };
    block1();
}
  1. 打开objc4-818.2源码,查找objc_retainBlock函数

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

    image.png
Block结构
image.png

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

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
image.png image.png

发现是一个全局block

修改代码,让block捕获外部变量
NSObject *objc1 = [NSObject alloc];
void (^block1)(void) = ^{
    NSLog(@"LG_Block %@", objc1);
};
block1();
image.png image.png image.png image.png image.png image.png

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

image.png

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

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

签名含义

对于签名v8@?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}

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;
}
  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;
}

小结:

lldb验证descriptor
image.png

block的捕获变量生命周期

在探索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

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 和 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
// 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;
image.png

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

// 结构体
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释放
// 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 和 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操作
// 对 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

static void __Block_byref_id_object_dispose_131(void *src) { 
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
上一篇下一篇

猜你喜欢

热点阅读