第4章 iOS内存管理:ARC原理、引用循环检测、内存泄漏排查、weak/strong/unowned

内存管理是iOS开发的基石,也是高阶面试中区分深度的关键。面试官会从ARC底层机制、循环引用场景、内存泄漏工具链以及weak/strong/unowned的语义差异四个维度考察。本章将帮你建立“原理+检测+修复”的系统化应答能力。

ARC原理回答框架 ARC本质:编译时插入 retain/release插入 autoreleasepool strong/weak/unsafe_unretained dealloc处理 回答时先点出编译器自动插入,再展开strong/weak差异,最后收束到autoreleasepool 循环引用检测 & 排查路径 怀疑循环引用来源 Instruments / MLeaksFinder 定位未释放对象 分析引用链/Backtrace block 捕获 self 代理 delegate NSTimer / CADisplayLink weak-strong-dance weak 声明 / invalidate 建议:现场演示 Xcode 内存图 + 手动泄漏检测

ARC 与 MRC 核心对比

维度ARCMRC
引用计数维护编译器自动插入 retain/release手动调用 retain/release/autorelease
ownership 修饰符strong / weak / unsafe_unretainedassign / retain / copy
autoreleasepool仍存在,由 @autoreleasepool 块控制手动管理或系统自动
循环引用风险weak 解决;unsafe_unretained 需谨慎assign 指向释放对象后成为野指针
dealloc不可手动调用 release,仅进行清理需调用 [super dealloc]

循环引用常见类型与修复策略

  • Block 循环:block 内部使用 self → weakSelf + strongSelf
  • 代理 (delegate):属性声明为 weak 或 assign(OC)
  • NSTimer:timer 持有 target → 手动 invalidate 或使用 block 版本(iOS 10+)
  • 大对象相互持有:检查全局单例或长期持有对象

问题一:ARC 原理——ARC 是如何管理内存的?

面试官考察点:考察对编译时插入 retain/release 的理解,以及是否知道 autoreleasepool 和 runloop 的关系。

回答框架:1. 点明 ARC 是编译器特性,在合适位置插入 retain/release;2. 说明 strong 增加引用计数,weak 不增加;3. 提及 autoreleasepool 延迟释放,以及 @autoreleasepool 块的作用;4. 可以补充 dealloc 时移除 weak 指针。

示例回答:ARC 在编译时分析对象生命周期,在赋值时插入 strong_retain,在变量作用域结束或重新赋值时插入 release。weak 变量通过 side table 维护计数表,对象释放时自动置 nil。autoreleasepool 将对象加入当前池,在池 drain 时统一 release。

可能追问:autoreleasepool 在 runloop 的哪个时机释放?weak 表是如何实现的?

避坑提醒:不要只说“不用写 retain/release”,要讲清楚编译器做了什么;同时要区分 ARC 与 GC 的区别。

问题二:循环引用检测——如何检测并修复循环引用?

面试官考察点:实际排查能力,是否用过 Instruments 或第三方工具,以及能否现场分析引用链。

回答框架:1. 先讲工具:Instruments 的 Allocations / Leaks,MLeaksFinder 等;2. 操作步骤:跑场景 → 抓内存快照 → 分析引用链;3. 修复:对持有的对象使用 weak 或手动释放。

示例回答:使用 Xcode 内存图调试定位未释放的 VC,左栏查看引用计数为 2 且无法 dealloc,在 Memory Graph 里点对象查看引用链,发现 block 强引用 self,导致 VC 无法释放。改用 weak-strong-dance 后解决。

可能追问:MLeaksFinder 原理是什么?如何检测 UIView 的循环引用?

避坑提醒:避免只背诵工具名而不演示操作流程;记得带上 weak-strong-dance 的代码写法示例。

问题三:weak / strong / unowned 的适用场景

面试官考察点:是否理解不同修饰符的生命周期语义,以及 unowned 的安全陷阱。

回答框架:1. strong:默认,增加引用计数;2. weak:不增加,对象释放后自动置 nil;3. unowned:不增加,且不置 nil,只在确定生命周期短于自己时使用;4. unsafe_unretained:类似 assign,不置 nil。

示例回答:weak 主要用于避免循环引用,比如 delegate 和 block 捕获的 weakSelf。unowned 用在 Swift 中闭包捕获确定不会被释放的对象,比如 self 生命周期比闭包长时,但若提前释放会 crash,所以优先用 weak。

可能追问:在 Swift 中 unowned 为什么可能比 weak 更高性能?什么场景下必须用 strong?

避坑提醒:不要把 unowned 和 weak 混淆;强调 unowned 的崩溃风险,不要主动推荐使用。

项目案例:VIP 视频播放模块内存优化

项目名称某在线教育 App 视频播放器
业务背景用户连续切换视频页面时 App 内存暴增,导致 OOM 崩溃,特别是 iOS 14 以下设备
规模指标日活 50 万,单次课程切换 3~5 次,崩溃率 0.3%
技术栈Swift + AVPlayer + UITableView + CacheManager + MLeaksFinder
核心难点AVPlayer 与 ViewController 之间形成循环引用,且 timer 未 invalidate;图片缓存强引用不回传
面试高频追问如何定位到 AVPlayer 循环?weak-strong-dance 在 block 中的写法?缓存引用如何用 weak 打破?
可迁移回答话术“我通过 Xcode 内存图发现播放器 VC 在 pop 后仍存活,View Hierarchy 显示 AVPlayerLayer 持有 self。检查 Player 的 block 回调中用 [weak self] 捕获,并在 dealloc 中 [timer invalidate] 解决。同时图片缓存改用 NSCache 自动释放。”

复习提示:建议结合 WWDC 2013 “iOS Memory Management” 和 Xcode 实际操作加深理解。面试前练习画引用链图,帮助现场表达。