5-4.【OC】【Block】Block 从 Stack copy 到 Heap 的触发时机有哪些?至少说出 3 种。

3 阅读2分钟

在 ARC(自动引用计数)环境下,编译器为了保证程序的安全性,会非常频繁地自动将 Block 从栈(Stack)拷贝到堆(Heap)上。一旦拷贝到堆,Block 的生命周期就不再受限于当前作用域,而是由引用计数管理。

以下是触发 Copy 操作的三个核心时机:

1. 将 Block 赋值给强引用对象

这是最常见的场景。当你将一个 Block 赋值给一个 __strong 修饰的变量(默认就是 __strong)或者类的一个属性(Property)时,编译器会自动调用 copy

Objective-C

// 这里的 block 变量默认是 __strong,会触发 copy
void (^myBlock)(void) = ^{
    NSLog(@"Hello World");
}; 

2. 作为函数的返回值(Return)

在 MRC 时代,如果函数返回一个栈上的 Block,外部调用者拿到的是一个瞬间销毁的指针。在 ARC 下,编译器会检测到 Block 作为返回值,并自动执行 copy,确保调用方拿到的是存储在堆上的安全副本。

Objective-C

typedef void(^Handler)(void);

Handler getBlock() {
    int x = 10;
    // 虽然这里看起来是在栈上创建,但 return 时会自动 copy 到堆
    return ^{ NSLog(@"%d", x); }; 
}

3. 作为某些系统 API 的参数

Cocoa Touch 框架中,很多带有 usingBlock: 字样的方法,或者 GCD(Grand Central Dispatch)的方法,内部都会对传入的 Block 进行 copy 以保证异步执行时的安全性。

  • GCD 方法: 例如 dispatch_async(queue, block);
  • 集合遍历: 例如 [array enumerateObjectsUsingBlock:...]

补充时机:__block 变量的作用

虽然这不直接是 Block 的 copy,但息息相关:当一个 Block 被拷贝到堆时,它所捕获的由 __block 修饰的变量也会被从栈拷贝到堆上,以确保变量和 Block 的生命周期同步。


总结对照

动作内存变化
定义在方法内且未赋值_NSConcreteStackBlock (栈)
执行 [block copy] 或强引用赋值_NSConcreteMallocBlock (堆)
未捕获外部变量 (或只捕获全局变量)_NSConcreteGlobalBlock (全局区)