✅「肉眼 + 编译器语义」判断法
这是苹果工程师自己用的方式。
一、先给结论(99% 场景适用)
你只需要看三件事:
🥇 是否捕获任何外部变量?
❌ 没有捕获:
void (^b)(void) = ^{
NSLog(@"hello");
};
👉 必然是:
✅ Global Block
这是最强规则。
不依赖上下文 ⇒ 编译期生成 ⇒ 全局唯一。
🥈 是否逃逸当前作用域?
比如:
返回:
- (dispatch_block_t)makeBlock {
return ^{
NSLog(@"hi");
};
}
赋给 strong:
self.block = ^{
NSLog(@"hi");
};
传入 async API:
dispatch_async(queue, ^{
});
👉 统统是:
✅ Heap Block
ARC 自动 copy。
🥉 局部使用 + 捕获变量 + 不 escape
void foo() {
int x = 10;
^{
printf("%d", x);
}();
}
👉 才是:
✅ Stack Block
这是唯一会出现 Stack Block 的情况。
二、工程速查表(记住这个)
| 特征 | 类型 |
|---|---|
| 无 capture | Global |
| capture + escape | Heap |
| capture + 不 escape | Stack |
你可以直接在脑子里写成:
if no capture → Global
else if escape → Heap
else → Stack
三、几个典型迷惑案例(你一定见过)
❓ case1:捕获 self 一定 Heap?
^{
[self doSomething];
}
如果只是:
^{
[self doSomething];
}();
同步调用:
👉 Stack
如果:
self.block = ^{
[self doSomething];
};
👉 Heap
不是 capture 决定 Heap,
是 escape。
❓ case2:dispatch_async 一定 Heap?
是的。
GCD API 标注:
dispatch_async(..., dispatch_block_t block);
block 必须逃逸。
ARC 插入:
objc_retainBlock
❓ case3:MASConstraintMaker 里的 block?
make.left.equalTo(self.view);
MASConstraint 内部保存 block:
👉 Heap
四、从汇编/ABI角度的铁证(高级)
如果你看 IR / asm:
Global:
__NSConcreteGlobalBlock
在 DATA 段。
Stack:
__NSConcreteStackBlock
在栈上构造 struct。
Heap:
出现:
objc_retainBlock
或:
_Block_copy
这是最终裁决标准。
五、运行时硬核判断(调试用)
你也可以写:
void printBlockType(id block) {
NSLog(@"%@ %@", block, [block class]);
}
输出:
__NSGlobalBlock__
__NSMallocBlock__
__NSStackBlock__
六、真正工程意义(10年开发级)
你其实不是在判断 block,
你是在判断:
👉 ARC 是否介入
Stack / Global:
零 retain
Heap:
必然:
- retain captured self
- retain captured obj
- 可能形成环
所以实战规则变成:
🚨 只要是 Heap Block:
就要考虑:
- self 是否循环引用
- 生命周期是否过长
- 是否需要 weak
七、一句话总结
你可以记成:
Block 类型不是由写法决定,而是由“是否捕获 + 是否逃逸”决定。