返回

iOS 底层揭秘:纵横捭阖,剖析 Block(四)——探索 Block 的底层奥秘与源码探析

IOS

Block 的底层奥秘:深入探索其原理和机制

在揭开 Block 的神秘面纱后,我们不禁好奇它的内部构造和运行机制。踏上探索之旅,让我们深入分析 Block 的底层原理,发掘其内在的奥秘。

Block 的结构解剖

从源码中,Block 的结构显露无遗:

struct Block_layout {
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    int flags;
    int reserved;
    void *captured[1];
};

仔细审视这个结构体,我们发现 Block 的关键组成部分:

  • invoke 函数指针: 指向 Block 实现代码的地址。
  • descriptor 指针: 指向 Block 符,包含元数据。
  • flags 标记 Block 的属性,如可捕获性、可变性。
  • reserved 保留字段。
  • captured 数组: 用于捕获外部变量。

Block 的类型演变

根据捕获变量的方式,Block 分化出不同的类型:

  • 全局 Block: 无外捕获,仅在栈上分配。
  • 栈 Block: 捕获栈变量,但不可修改。
  • 堆 Block: 捕获栈变量,并可修改。

Block 的调用机制

当调用一个 Block 时,系统会采取以下步骤:

  1. 检查 Block 的 flags,确定其类型。
  2. 全局 Block 直接调用 invoke 函数。
  3. 栈 Block 或堆 Block 创建副本后,再调用副本的 invoke 函数。
  4. Block 的 invoke 函数执行代码,访问捕获变量。

源码细读:Block 的实现原理

深入 Block 的源码,我们发现它的实现精妙:

在头文件 Block_private.h 中,定义了 Block 符的结构体:

struct Block_descriptor_1 {
    unsigned long int reserved;
    unsigned long int size;
    const char *copy_helper;
    const char *dispose_helper;
    const char *invoke;
    const char *layout;
};

其成员包含 Block 的元数据,如大小、复制、释放和调用帮助函数。

在源文件 Block_private.c 中,实现了 Block 的各种操作函数:

static inline void *
Block_copy(const void *arg)
{
    Block_layout *block = (Block_layout *)arg;
    Block_layout *newBlock =
        Block_copy_create(Block_size(block), block->invoke,
                        block->descriptor);
    memcpy(newBlock, arg, Block_size(block));
    return newBlock;
}

Block_copy() 函数负责创建 Block 副本,复制内容到新副本中。

总结:掌控 Block 的真谛

通过揭开 Block 的底层原理和源码,我们深入理解了其内部结构、类型、调用机制。这些知识将帮助我们在 iOS 开发中游刃有余地使用 Block,提升编程效率。

Block 使用中的常见问题解答

问:如何捕获外部变量?
答:在 Block 的 captured 数组中声明变量。

问:如何复制 Block?
答:使用 Block_copy() 函数创建副本。

问:何时使用堆 Block?
答:当需要修改捕获的变量时。

问:Block 的调用机制有哪些限制?
答:复制 Block 会引入开销,而堆 Block 的释放需要手动管理。

问:如何高效使用 Block?
答:考虑 Block 的类型和调用成本,并避免过度捕获变量。