xcode内存图可实时定位循环引用,需运行app后手动抓图,关注红色双向箭头、非零引用计数及调用栈,修复闭包捕获、定时器、代理等常见场景。
直接在 xcode 运行时点一下按钮就能看到谁卡着没释放,循环引用基本一目了然。
启动 Memory Graph 的正确姿势
别等 App 崩了才想起来查——得让它跑起来、操作完目标场景(比如 push/pop 页面、触发闭包、停止定时器),再手动抓图。
- 确保项目已运行(模拟器或真机均可),且处于调试状态
- 点击 Xcode 调试区域底部的 Debug Memory Graph 按钮(图标是两个重叠的圆圈,靠近控制台上方)
- 点击后会暂停 App 执行,自动生成当前堆内存的对象关系图
- 若右侧看不到调用栈,记得提前在 Product → Scheme → Edit Scheme → Run → Diagnostics 中勾选 Malloc Stack(否则无法定位到哪行代码分配的对象)
快速识别循环引用的关键线索
图中不是所有连线都危险,重点盯住红色实线箭头和成对互指的节点——这是强引用循环的典型信号。
利用 macOS 原生能力实现本地语音识别与合成。通过 yap (Apple Speech.framework) 进行语音转文字,通过 say + ffmpeg 进行文字转语音。完全离线,无需 API 密钥。具备音质检测与智能选声功能。
- 找两个对象之间有双向红色箭头(比如 ViewController ↔ MyView,或 A ↔ B)
- 查看节点右上角引用计数:非零且本该为 0(比如 pop 后 ViewController 仍存在)
- 左侧 Debug Navigator 点击漏斗图标,筛选 Leaks 或搜索关键词如 Closure captures、Retain Cycle
- 选中可疑对象,右侧面板会显示完整调用栈,直接跳转到初始化/赋值位置(比如闭包捕获 self 的那行)
常见循环引用场景与修复方向
不是所有“互相持有”都写在类声明里,很多藏在闭包、定时器、代理或容器结构中。
-
闭包捕获 self:传入的闭包里用了
self.xxx却没加[weak self]—— 改成{ [weak self] in self?.label.text = "xxx" } -
定时器未失效:NSTimer 强持有 target,pop 前必须调用
timer.invalidate()并置 nil - 容器互相持有:比如两个 NSMutableArray 互相 addObject,这类需检查逻辑是否真需要双向持有,或改用 weak 引用容器
-
delegate 未设 weak:协议属性必须声明为
weak,尤其 UIViewController 和子视图之间
辅助判断与导出分析
单靠一张图有时不够,特别是多层嵌套引用。可以借助筛选和协作手段缩小范围。
- 右键节点 → Focus on… 只保留相关对象及其引用链,排除干扰
- File → Export Memory Graph,保存 .mmg 文件发给同事一起看
- 配合 Allocations 工具:在 Instruments 中开启 Call Tree,按 “Live Bytes” 排序,找到长期存活的大对象再回 Xcode 查图
- 真机调试更可靠:部分模拟器行为(如 viewDidDisappear 触发时机)可能掩盖问题










