10-主题|内存管理@iOS-Block内存管理

5 阅读6分钟

本文专门介绍 Objective-C BlockSwift 闭包内存管理:Block 的三种类型(全局/栈/堆)、捕获变量与内存、copy 语义、循环引用 与破除,以及作为属性/参数时的注意点。前置知识见 04-ARC详解06-weak与循环引用


一、Block 是什么(与内存的关系)

  • Block 是 Apple 对 C 语言扩展的闭包:可捕获外部变量、作为对象参与引用计数;在内存上既包含代码(函数指针),也包含捕获的变量(结构体形式),因此既有「存在位置」(栈/堆/全局)也有「对捕获对象的持有关系」。
  • 内存管理 需关注两点:Block 对象本身 的分配与释放(栈 block / 堆 block / 全局 block),以及 Block 对捕获变量(尤其是 OC 对象) 的强引用/弱引用,避免循环引用与泄漏。

二、Block 的三种类型与内存位置

2.1 类型与存储位置

类型(运行时 isa)存储位置产生条件(典型)
NSGlobalBlock全局区(.data/.text)未捕获任何外部变量(或仅捕获全局/静态变量)
NSStackBlock捕获了自动变量(局部变量),且未 copy 到堆(MRC 下常见)
NSMallocBlock对栈 block 执行 copy,或 ARC 下多数「需要逃逸」的 block 被编译器自动 copy 到堆
  • 全局 Block:不依赖栈帧,无需 copy,可当作单例使用。
  • 栈 Block:随栈帧销毁而失效,若要在作用域外使用(如存为属性、异步回调),必须先 copy 到堆;ARC 下编译器会在赋值给 strong/copy 属性、跨函数传递等场景自动插入 copy。
  • 堆 Block:参与引用计数,由 ARC/MRC 管理;copy 时引用计数 +1,release 时 -1。

2.2 简单判断示例(ARC)

// 无捕获 → 全局 Block(__NSGlobalBlock__)
void (^gBlock)(void) = ^{ NSLog(@"no capture"); };

// 捕获局部变量 → 栈 Block(__NSStackBlock__),若赋给 strong/copy 属性则会被 copy 成堆 Block
int a = 1;
void (^sBlock)(void) = ^{ NSLog(@"%d", a); };
// 赋值给 copy/strong 属性或作为参数传给需要「持有」的 API 时,会变成 __NSMallocBlock__

2.3 MRC 下 Block 的 copy 必要性

  • MRC 下,栈上的 Block 在函数返回后栈帧被回收,若此时 block 已被传给调用方或存到堆对象(如属性),再执行会野指针/未定义行为
  • 因此 MRC 下:凡是需要跨作用域保留的 block,必须对其执行一次 copy,将栈 block 拷贝到堆上,得到 NSMallocBlock,之后按普通 OC 对象做 retain/release;用完后要对堆 block 做 release(或 autorelease)。
  • ARC 下:编译器在「赋值给 strong/copy 属性、作为参数传给会保留 block 的 API」等场景自动插入 copy,一般无需手写 [block copy]

三、Block 捕获变量与内存

3.1 捕获方式概览

捕获对象/变量默认行为(OC 对象)对引用计数的影响
局部 OC 对象(自动变量)强引用(strong)Block 被 copy 到堆时,会 retain 被捕获的对象;block 释放时 release
局部标量(int、结构体等)值拷贝不涉及引用计数
__block 修饰的变量生成结构体,block 与外部共享若 __block 变量指向 OC 对象,需注意 MRC/ARC 下 retain 行为;__block 可改写
__weak 修饰的对象弱引用Block 不持有该对象,不增加引用计数,可避免循环引用

3.2 对象捕获与循环引用

  • Block 若强引用了某个对象 A(如直接使用 self),而 A 又强引用了该 block(如 block 被 A 的 strong/copy 属性持有),则形成 self → block → self 的循环,两者都不会释放。
  • 解决:在 block 外使用 __weak typeof(self) wself = self,block 内使用 wself,这样 block 对 self 是弱引用;若在 block 执行过程中担心 self 被释放,可在 block 内再用 __strong typeof(wself) sself = wself 强引用一次(仅限 block 执行期),避免执行到一半 self 被置 nil。详见 06-weak与循环引用

3.3 __block 与内存(简述)

  • __block 使局部变量在 block 内可被修改,编译器会生成一个包装结构,block 捕获的是该结构;若 __block 变量指向 OC 对象,在 ARC 下通常不会造成 block 对对象的强引用(对象存在 __block 结构里),但若在 block 内给该变量赋新值,会涉及旧值 release、新值 retain。MRC 下 __block 不会自动 retain 对象,需自行管理。
  • 历史上用 __block 打破循环(__block self + block 内置 nil)的写法在 ARC 下不推荐,应使用 __weak 打破循环。

四、Block 作为属性、参数与返回值

4.1 属性声明

属性修饰说明
copy设值时对 block 执行 copy;MRC 时代推荐,ARC 下 strong 与 copy 对 block 效果类似(都会 copy 到堆),习惯上仍常用 copy 表达「这是 block」的语义。
strongARC 下与 copy 类似,赋值时也会把栈 block copy 到堆并强引用。
  • Block 属性应避免用 assign(栈 block 离开作用域后失效,会野指针)。

4.2 作为参数与返回值

  • 作为参数:若 API 会保存 block(如延迟执行、存入数组),API 内部应对传入的 block 做 copy(或由 ARC 在传入时保证是堆 block);调用方传栈 block 时,由被调用方 copy 到堆是常见约定。
  • 作为返回值:返回 block 时,若希望调用方在函数返回后仍能使用,应返回堆上的 block(MRC 下 return 前对 block 做 copy/autorelease;ARC 下编译器会根据返回类型自动处理)。

五、ARC 与 MRC 下 Block 内存小结

场景MRCARC
栈 block 需跨作用域使用必须对 block 执行 copy,用完后 release编译器在赋值给 strong/copy、传参等场景自动 copy
Block 属性copy,setter 里对 block copy、对旧 block releasecopystrong 均可,都会导致 copy 到堆并强引用
Block 内引用 self避免 self→block→self:用 __weak 或 __block+置 nil__weak self,必要时 block 内 __strong 一次
Block 捕获的 OC 对象copy 到堆时 block 会 retain 捕获的对象;block release 时 release 这些对象同左,由编译器插入

六、流程图:Block 从创建到释放(概念)

flowchart LR
    A[定义 Block] --> B{是否捕获自动变量?}
    B -->|否| C[__NSGlobalBlock__ 全局]
    B -->|是| D[__NSStackBlock__ 栈]
    D --> E[赋值给 strong/copy 或 传参]
    E --> F[copy 到堆 __NSMallocBlock__]
    F --> G[Block 被 release]
    G --> H[对捕获对象 release]

七、Swift 闭包与内存

  • Swift 闭包 与 OC Block 语义对应:闭包会捕获外部变量,默认对类对象是强引用
  • 循环引用:若对象强引用闭包,闭包内又使用了 self(或捕获了 self),则形成循环;解决方式为在闭包捕获列表中写 [weak self][unowned self](后者在 self 一定不会先于闭包释放时使用,否则会野指针)。
  • @escaping:标记闭包会「逃逸」出当前函数(如异步回调),编译器会按需将闭包拷贝到堆上,与 OC 中「block 被 copy 到堆」对应。

八、思维导图:Block 内存管理知识结构

mindmap
  root((Block 内存管理))
    三种类型
      全局 Block 无捕获
      栈 Block 捕获未 copy
      堆 Block copy 后
    捕获与引用
      对象默认强引用
      __weak 破循环
      __block 可改写
    属性与生命周期
      copy/strong 属性
      MRC 需手写 copy
      ARC 自动 copy
    循环引用
      self → block → self
      weak self strong self

九、参考文献