Detect and diagnose memory issues
Developer Tools 进阶 25m

检测和诊断内存问题

Detect and diagnose memory issues

2021年6月8日

在 Apple 官方观看视频

一句话判断

如果你的 App 被系统杀死但你不确定为什么,Xcode 13 的内存诊断工具能帮你找到答案——从”系统告诉我内存压力”到”我知道是哪行代码泄漏了”之间的差距,这场 Session 帮你填上。

这场 Session 讲了什么

Session 系统性地讲解了如何在 iOS 和 macOS 上检测、诊断和修复内存问题。覆盖了三类常见的内存问题:内存泄漏(Memory Leak,已分配的内存无法被释放)、废弃内存(Abandoned Memory,仍然被引用但不再需要的数据)、和过度分配(Over-allocation,一次性分配了远超需要的大块内存)。

工具方面,Session 重点介绍了 Xcode 13 中改进的 Memory Graph Debugger、Instruments 的 Allocations 和 Leaks 模板、以及新的 os_proc_available_memory() API。Memory Graph Debugger 现在可以导出内存图快照(memgraph 文件),然后用命令行工具 leaksheapvmmap 做深度分析。这种”GUI 抓快照 + CLI 做分析”的工作流比在 Xcode GUI 里翻来翻去高效得多。

值得深挖的点

memgraph 文件的命令行分析

memgraph 文件可以用三个命令行工具分析:leaks 查找循环引用和未释放的对象、heap 按类型统计内存占用、vmmap 分析虚拟内存区域分布。最实用的是 leaks --referenceTree——它会以树状结构展示泄漏对象的引用链,从根对象到泄漏对象一路标注,让你一眼看出是哪个属性持有导致了泄漏。heap --sortBySize 则按总大小排列所有类型,帮你快速找到”哪个类吃了我最多的内存”。

os_proc_available_memory() 的实际应用

iOS 15 新增了 os_proc_available_memory() 函数,返回当前进程还可以使用多少字节内存。你可以在关键操作前检查这个值,如果可用内存不足就提前降级(比如降低图片缓存的上限、清理不必要的缓存)。这比等到系统发 didReceiveMemoryWarning 再处理更优雅——后者意味着系统已经在考虑杀你了。

代码片段

// 使用 os_proc_available_memory() 监控内存压力
import os

class MemoryMonitor {
    static let shared = MemoryMonitor()
    
    /// 检查当前可用内存,返回是否处于安全范围
    func checkMemoryPressure() -> Bool {
        let available = os_proc_available_memory()
        let availableMB = Double(available) / 1024 / 1024
        
        print("可用内存: \(String(format: "%.1f", availableMB)) MB")
        
        // 低于 100MB 时触发降级策略
        if available < 100 * 1024 * 1024 {
            triggerMemoryCleanup()
            return false
        }
        
        return true
    }
    
    private func triggerMemoryCleanup() {
        // 清理图片缓存
        ImageCache.shared.clearAll()
        
        // 释放不必要的资源
        DataCache.shared.trim(to: 10 * 1024 * 1024) // 降到 10MB
        
        print("内存压力触发清理")
    }
}
# 在 Xcode 中导出 memgraph 文件
# Debug Navigator > Memory > 点击齿轮图标 > Export Memory Graph
# 或使用命令行:
# leaks --referenceTree my_app.memgraph > leaks_report.txt

# 查找循环引用
leaks my_app.memgraph

# 按内存占用排序所有类型
heap --sortBySize my_app.memgraph | head -20

# 分析虚拟内存布局
vmmap --summary my_app.memgraph

# 组合使用:查找某个类的所有实例和引用链
leaks --referenceTree --traceTree my_app.memgraph | grep "UIImageView"
// 使用 Instruments 的 Allocations 模板追踪特定对象的分配
// 在代码中标记关键分配点
import os.signpost

let log = OSLog(subsystem: "com.example.myapp", category: "Memory")

func loadLargeImage(url: URL) {
    os_signpost(.event, log: log, name: "ImageAlloc", "开始加载: %{public}s", url.lastPathComponent)
    
    // 图片加载逻辑
    let task = URLSession.shared.dataTask(with: url) { data, _, _ in
        if let data = data {
            let imageSize = data.count
            os_signpost(.event, log: log, name: "ImageAlloc", 
                        "加载完成,大小: %ld bytes", imageSize)
        }
    }
    task.resume()
}

最佳实践

  • 在 App 开发过程中每周做一次 memgraph 快照分析:在典型使用场景下(浏览几个页面、创建一些内容)抓一个快照,用 heap --sortBySize 检查是否有异常大的内存占用。
  • os_proc_available_memory() 在加载大资源(图片、视频、3D 模型)前调用。如果可用内存不足,先清理缓存再加载——比加载失败后再处理用户体验更好。
  • Leaks 和 Abandoned Memory 要分开检测:Leaks 用 Instruments 的 Leaks 模板自动检测,Abandoned Memory 用 “Mark Heap” 功能对比两个时间点的内存图差异。

还有什么值得关注

  • Xcode 13 的 Memory Graph Debugger 新增了对 Swift closure 捕获列表的可视化——closure 捕获了哪些变量一目了然,是排查 closure 循环引用的利器。
  • vmmap--verbose 模式可以展示每个 VM region 的详细属性,包括是否被 swapped、是否是 mmap 文件。
  • 在 macOS 上可以用 malloc_history 命令追踪某个内存地址的完整分配调用栈。
WWDC 2021