这是个**非常底层 + 很“历史包袱感”**的问题 ——Block 之所以看起来怪(Global / Stack / Heap 三种),本质不是设计优雅,而是:
👉 C 语言 + Objective-C 运行时 + ARC 后补丁式演进的产物。
一、最初:Block 不是 OC 发明的
Block 起源是 Clang / LLVM 给 C 语言加的扩展(≈2008)
当时目标:
✅ 给 C 增加闭包能力
✅ 不破坏原有 ABI
✅ 能被 C / C++ / Objective-C 共用
所以:
Block 从第一天开始就不是 NSObject。
而是:
struct Block_literal {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
};
也就是说:
Block 本质是:
👉 一个 C struct
二、为什么默认是 Stack Block?
因为 C 闭包的最自然语义是:
void foo() {
int x = 10;
void (^blk)(void) = ^{
printf("%d", x);
};
}
在 C 世界:
局部变量存在:
👉 栈上
所以早期设计是:
Block literal 创建在当前栈帧
也就是:
_NSConcreteStackBlock
这是最低成本方案:
✔ malloc 不需要
✔ 性能最好
✔ 生命周期跟函数一致
但问题来了:
return blk;
Boom 💥
栈销毁了。
Block 还被引用。
所以苹果加了:
Block_copy
也就是:
👉 把 Stack Block 拷贝到 Heap
这就是:
_NSConcreteMallocBlock
后来 ARC 把它包装成:
copy
三、Global Block 又是什么鬼?
这是个编译期优化产物。
如果:
Block 没捕获任何变量:
void (^blk)(void) = ^{
NSLog(@"hello");
};
那么:
👉 它完全不依赖栈
👉 没有 captured context
于是 LLVM 直接生成:
_NSConcreteGlobalBlock
特点:
✅ 编译期生成
✅ 在 DATA 段
✅ 进程级单例
✅ 永不释放
类似 C 函数指针。
所以:
| 类型 | 本质 |
|---|---|
| Global | 静态函数对象 |
| Stack | 临时闭包 |
| Heap | 持久闭包 |
四、历史演进时间线(关键)
2008
Clang 推出 Blocks
只有:
- Stack
- Global
必须手动:
Block_copy
2010(iOS4)
引入:
_NSConcreteMallocBlock
开发者开始写:
[block copy]
2011(ARC)
ARC 自动插入:
objc_retainBlock
规则:
👉 escaping block 自动 copy
这时 Heap Block 才真正普及。
Swift 出现后(2014+)
Swift closure 一开始就是 heap allocated。
Objective-C Block 三分法彻底变成历史包袱。
五、为什么不直接统一成 Heap?
这是很多人问的。
原因只有一个:
ABI 兼容
Block 是:
- C ABI
- 系统框架
- 大量旧 App
一旦改:
全世界崩。
而且:
Stack Block 有明显优势:
性能
栈分配:
👉 零 malloc
👉 零 atomic retain
👉 零 cache miss
苹果在 CoreAnimation / UIKit 内部仍大量使用 Stack Block。
六、真实底层判定逻辑
你可能见过:
NSLog(@"%@", block);
内部其实是:
block->isa ==
&_NSConcreteStackBlock
&_NSConcreteMallocBlock
&_NSConcreteGlobalBlock
isa 是静态地址。
不是 runtime class。
七、ARC 下真实规则(面试常考)
1️⃣ 默认:
^{ ... }
是 Stack
2️⃣ 一旦 escape:
- 返回
- 赋给 strong
- 作为参数传出当前 scope
自动 copy → Heap
3️⃣ Global 永远不 copy
判断方法:
NSLog(@"%@", [block class]);
八、总结一句话(给十年开发)
Block 的三态不是设计选择,而是 C 闭包向 ARC 时代妥协的历史产物。
本质演进:
C stack closure
↓
manual copy
↓
ARC auto copy
↓
Swift heap closure
Objective-C 只是夹在中间。
九、工程上的真正意义
今天你关心的只有:
✅ Stack Block 极快但不能逃逸
✅ Heap Block 有 ARC 成本
✅ Global Block 是纯函数对象
以及: