跳到主要内容

内存泄漏排查

问题

如何检测和排查 iOS 应用的内存泄漏?有哪些工具和方法?

答案

内存泄漏的类型

类型说明检测方式
循环引用两个对象互相强引用Memory Graph / Leaks
闭包捕获闭包捕获 self 未用 weakMemory Graph
Timer 未释放repeating Timer 持有 targetdeinit 日志
通知未移除iOS 9 前需手动移除代码审查
C 层内存malloc 未 freeAllocations

工具一:Memory Graph Debugger

Xcode 调试时点击 Debug Memory Graph 按钮:

  • 紫色感叹号标记泄漏对象
  • 可视化显示对象的引用关系图
  • 点击对象查看持有链(谁 retain 了它)
最实用的排查方法
  1. 进入某个页面 → 操作 → 退出页面
  2. 点击 Memory Graph
  3. 搜索该页面的 ViewController → 如果还存在,说明泄漏
  4. 查看引用链找到循环引用源头

工具二:Instruments - Leaks

Product → Profile → Leaks
  • 自动检测运行时泄漏(周期性扫描堆内存)
  • 显示泄漏对象的分配堆栈
  • 只能检测到真正的孤立循环,单个未释放对象检测不到

工具三:Instruments - Allocations

  • 查看所有内存分配 / 释放的详细记录
  • Mark Generation:标记内存快照,对比两次快照之间的增量
  • 适合排查内存持续增长(不一定是泄漏,可能是缓存未清理)

工具四:deinit 打印

class DetailViewController: UIViewController {
deinit {
print("✅ DetailViewController deinit")
}
}

简单有效:如果退出页面后没看到 deinit 日志 → 泄漏。

常见泄漏模式与修复

// 1. 闭包捕获
viewModel.onComplete = { [weak self] result in
self?.handleResult(result)
}

// 2. Timer
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
self?.update()
}

// 3. delegate
protocol MyDelegate: AnyObject { } // AnyObject 约束才能 weak
weak var delegate: MyDelegate?

// 4. NotificationCenter (block API)
let token = NotificationCenter.default.addObserver(forName: .myNote, object: nil, queue: .main) { [weak self] _ in
self?.refresh()
}
// 记得移除
NotificationCenter.default.removeObserver(token)

常见面试问题

Q1: Memory Graph 和 Leaks 有什么区别?

答案

  • Memory Graph:手动触发的快照,显示当前所有对象的引用关系,可以找到任何不应该存在的对象
  • Leaks:自动周期性扫描,只能检测孤立的循环引用(无法从 root 到达的环),对于被 window 间接持有的泄漏可能检测不到

Q2: 如何排查闭包导致的内存泄漏?

答案

  1. deinit 中打印日志,确认 VC 是否释放
  2. 如果未释放,打开 Memory Graph,搜索该 VC
  3. 查看引用链中是否有 closure 持有 self
  4. 在闭包的 capture list 中添加 [weak self]

相关链接