5-1.【OC】【Block】OC 中为什么会有 Global / Stack / Heap 三种 Block?它们诞生的历史原因是什么?

4 阅读3分钟

这是个**非常底层 + 很“历史包袱感”**的问题 ——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 是纯函数对象

以及:

retain self 的永远是 Heap Block