从底层 C 语言的定义来看,Block 的本质是一个结构体。无论它是哪种类型的 Block,其内存布局的首部都遵循统一的规范(通常被称为 Block_layout)。
以下是 Block 内存布局中最重要的几个关键字段:
1. isa 指针
- 作用: 指向 Block 的类对象。
- 意义: 决定了这是一个环境 Block 的类型(
_NSConcreteStackBlock、_NSConcreteMallocBlock或_NSConcreteGlobalBlock)。正是因为有了isa,Block 才能被视为 Objective-C 对象并响应retain/release。
2. flags (标志位)
-
作用: 一个整数位掩码(Bitmask)。
-
内容: 存储了 Block 的各种状态信息,例如:
- 该 Block 是否正在执行
copy操作。 - 是否捕获了外部变量。
- 是否有
signature(签名)信息。 - 是否包含辅助函数
copy_helper和dispose_helper。
- 该 Block 是否正在执行
3. reserved (保留字段)
- 作用: 预留空间,目前通常为 0。
4. invoke (函数指针)
- 作用: 这是 Block 最核心的部分。
- 内容: 它指向 Block 内部代码块编译后生成的具体 C 函数地址。当你“调用”一个 Block 时,程序实际上是跳转到这个指针所指向的地址。
5. descriptor (描述结构体)
-
作用: 指向一个由编译器生成的
Block_descriptor结构体。 -
内容: *
reserved: 同样是保留位。size: Block 占用的总内存大小。- 可选字段: 如果 Block 捕获了对象或
__block变量,这里还会包含copy和dispose两个辅助函数的指针,用于在 Block 拷贝到堆或销毁时进行正确的内存管理。
6. 捕获的变量 (Captured Variables)
- 位置: 紧跟在
descriptor指针之后。 - 内容: 如果 Block 引用了外部变量,编译器会将这些变量(或其指针、或
__block结构体)依次排列在 Block 布局的末尾。
Block 内存布局示意图
| 偏移量 | 字段名 | 说明 |
|---|---|---|
| 0 | isa | 类对象指针 (8 bytes) |
| 8 | flags | 状态标志位 (4 bytes) |
| 12 | reserved | 保留位 (4 bytes) |
| 16 | invoke | 编译后的代码实现指针 (8 bytes) |
| 24 | descriptor | 辅助信息结构体指针 (8 bytes) |
| 32+ | Captured Vars | 捕获的变量(值或指针,大小不固定) |
一个容易被忽视的细节
在 descriptor 结构体中,如果 flags 指出有签名信息,那么在 size 之后还会隐藏一个 signature 字符串指针。这个签名对于 消息转发(Message Forwarding) 和一些 Hook 框架(如 Aspect)至关重要。