5-2.【OC】【Block】如何通过代码特征快速判断一个 Block 属于哪种类型?

2 阅读2分钟

✅「肉眼 + 编译器语义」判断法

这是苹果工程师自己用的方式。


一、先给结论(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 的情况。


二、工程速查表(记住这个)

特征类型
无 captureGlobal
capture + escapeHeap
capture + 不 escapeStack

你可以直接在脑子里写成:

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 类型不是由写法决定,而是由“是否捕获 + 是否逃逸”决定。