附录-OC源码-Block:Block 的源码解析

源码下载地址: Open Source
我使用的版本: libclosure-74.tar
这篇文章阅读前,先看编译后的 block 对理解会有帮助: 其他: clang 编译后的的 block 解析

之前在编译后的代码中提到过, block 本质是一个结构体
生成的结构体,在源码中对应的是 struct Block_layout
(ps.个人推测, 编译后的结构体是用源码中的结构体由编译拼接出来的)

数据结构

虽然类似的图以及在很多文章出现过了,但还是自己画一遍加深印象. 整体结构非常简单

Block_layout

就是 block 被编译后的数据结构, 其中 isa 的指向源码中没有提供(只有几个数组)

  • isa 根据存储的内存区域(堆,栈,全局)不同而有不同的值(应该没开源)
    (源码中定义是个数组, 使用时也指向这个数组,很奇怪)
  • flags: 下图的匿名结构体(注意这个 enum 可以同时是几个值的合体, 看到位控制了没,不同的值只要取不同 bit 就可以都表示)
    • 其中BLOCK_NEEDS_FREE 代表需要释放, 从后面函数中使用来看, 就是放在堆的意思
    • BLOCK_HAS_COPY_DISPOSE代表有 copy 和 dispose 函数, 就是 descriptor2 中那两个字段
  • invoke: 函数指针, 指向代码中定义 block 函数体(编译后的 block 函数体会被定义成全局函数)
  • descriptor: 一些描述信息 (里面带了变量的复制和销毁函数)
    • 可以看到这里被分为 1,2,3 分别存了不同的信息
    • 这些形式在实际使用中,通过 flag 配合内部地址偏移可以访问到
    • 2 对应flagBLOCK_HAS_COPY_DISPOSE, 3 对应 flag BLOCK_HAS_SIGNATURE
  • imported variables: 我们在编译后的代码中看到其他: clang 编译后的的 block 解析, block 中使用的外界局部变量,都会被编译器转换成结构体的字段,存放在这里面
struct Block_layout {  
    void *isa;  
    volatile int32_t flags; // contains ref count  
    int32_t reserved;  
    BlockInvokeFunction invoke;  
    struct Block_descriptor_1 *descriptor;  
    // imported variables  
};  
enum {  
    BLOCK_DEALLOCATING =      (0x0001),  // runtime  
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime  
    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  
};  

Block_byref

之前探究编译后结果的文章其他: clang 编译后的的 block 解析, 基本数据类型要在 block 中被修改. 需要加上__block 标记.
这个标记加上后, 编译的结果就是基本数据类型被封装成struct Block_byref
ps.下面的结构体应该比实际生成的结构体是要少了 基本数据类型本身 这个字段(比如前文的 int mainInt 也在这个结构体中)

  • isa:
  • forwarding: 在栈中指向自己, 其使用和 copy 有关
  • flags: 既下面的enum(注意这个 enum 可以同时是几个值的合体)
struct Block_byref {  
    void *isa;  
    struct Block_byref *forwarding;  
    volatile int32_t flags; // contains ref count  
    uint32_t size;  
};  
// 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_copy: 对 Block 的复制函数

总共 4 个分支(入参空否,处理 BLOCK_NEEDS_FREE, 处理BLOCK_IS_GLOBAL, 处理剩下情况)

  1. 对要复制的入参判空,
  2. 判断 flag(前面数据结构的 flags 就是做这个用的)
    • 在堆中的 block, 引用计数+1
    • 在全局的 block, 直接 return
    • 其他情况下, 在堆中申请一块内存, 然后逐个字段赋值, 改 flags,引用计数赋值
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;  
    if (aBlock->flags & BLOCK_NEEDS_FREE) {  
        // latches on high  
        latching_incr_int(&aBlock->flags);  
        return aBlock;  
    }  
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {  
        return aBlock;  
    }  
    else {  
        // Its a stack block.  Make a copy.  
        struct Block_layout *result =  
            (struct Block_layout *)malloc(aBlock->descriptor->size);  
        if (!result) return NULL;  
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first  
#if __has_feature(ptrauth_calls)  
        // Resign the invoke pointer as it uses address authentication.  
        result->invoke = aBlock->invoke;  
#endif  
        // reset refcount  
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed  
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1  
        _Block_call_copy_helper(result, aBlock);  
        // Set isa last so memory analysis tools see a fully-initialized object.  
        result->isa = _NSConcreteMallocBlock;  
        return result;  
    }  
}  

_Block_release : 对 Block 的释放函数

判空 -> 判断是不是 global -> 判断是否堆中的 -> 判断引用计数 -> 调用 dispose_helper(销毁内部的变量) -> 释放

void _Block_release(const void *arg) {  
    struct Block_layout *aBlock = (struct Block_layout *)arg;  
    if (!aBlock) return;  
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;  
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;  
  
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {  
        _Block_call_dispose_helper(aBlock);  
        _Block_destructInstance(aBlock);  
        free(aBlock);  
    }  
}  

_Block_object_assign: 对 block 中参数的复制操作

_Block_object_dispose: 对 block 中参数的销毁操作

内部都是 switch-case,用于对不同的外部变量做内部的复制和释放处理用. 前面数据结构的flags 就用来标记这个
源码没啥内容, 直接上源码吧,就不贴了
runtime.cpp