Memory Graph — Xcode调试导航器的可视化工具,显示应用程序内存中对象及其相互引用的关系图。与堆转储不同,Memory Graph不仅显示对象列表,还显示有向引用图,其中每个节点是一个对象,每条边是一个引用(强引用、弱引用、无主引用)。根据Apple WWDC 2018,该工具可以在几秒内直观地检测循环引用和内存泄漏,无需分析堆转储的原始数据。
要点
Memory Graph — 是Xcode调试导航器的一个组件(出现在Xcode 10、WWDC 2018中),它构建被调试进程内存中所有对象的有向图。图的每个节点是一个类实例(Objective-C或Swift),每条边是对另一个对象的引用。边的颜色指示引用类型:蓝色 — 强引用,绿色 — 弱引用,灰色 — 无主引用。该图基于LLDB和Objective-C运行时的数据构建,因此为了正常运行,应用程序必须在Debug配置下编译并启用符号。
工作原理:当应用程序在断点处暂停时,Xcode通过LLDB向运行时请求所有活动对象及其引用。LLDB使用objc_getClassList并迭代分配区域来构建完整的图。在ARM64(Apple Silicon)上,额外使用硬件手段来跟踪分配而不会降低速度。图的构建时间取决于堆大小:对于典型的iOS应用程序(50–200 MB),图在1–3秒内构建完成。
根据Apple的说法,Memory Graph是唯一可以在不修改代码或添加检测工具的情况下可视化循环引用的工具。与Instruments Leaks不同,Memory Graph在Xcode内部实时工作,不需要单独启动分析器。这使其成为开发过程中快速诊断内存泄漏的首选工具。
堆转储提供所有对象及其数字(浅大小、保留大小)的表格 — 适用于定量分析。Memory Graph提供连接的可视化图像 — 适用于查找循环引用。工具相互补充:首先使用Memory Graph快速检测循环引用,然后通过Instruments Allocations使用堆转储精确测量保留大小。根据objc.io的经验,两种方法的组合覆盖了95%的内存泄漏场景。
循环引用 — 两个或多个对象通过强引用相互持有,形成闭合轮廓的情况。ARC无法释放这样的轮廓,因为每个对象的引用计数永远不会达到零。经典示例:ViewController和View,其中View拥有对闭包的强引用,而该闭包捕获了self(ViewController)。Memory Graph以环的形式显示此类轮廓,并高亮显示以便快速识别。
当Xcode检测到循环引用时,它会用橙色轮廓高亮显示并在调试导航器中显示警告。点击该循环,您将看到形成闭合轮廓的引用链。开发人员需要确定哪个强引用边应该是弱的 — 通常是从子对象到父对象的引用(例如,delegate或closure)。
class ViewController: UIViewController {
let service = DataService()
override func viewDidLoad() {
super.viewDidLoad()
// ❌ 循环引用:ViewController → 服务 → 闭包 → ViewController
service.fetchData { self.updateUI($0) }
}
func updateUI(_ data: Data) {}
}
class DataService {
var completion: ((Data) -> Void)?
func fetchData(handler: @escaping (Data) -> Void) {
self.completion = handler
}
}
在Memory Graph中,您将看到一个三角形:ViewController → DataService → closure → ViewController。解决方案 — 弱捕获self:[weak self]。修复后,Memory Graph将显示从闭包到ViewController的绿色边,循环引用将消失。
// 修正后的代码 — 弱捕获self
service.fetchData { [weak self] data in
guard let self else { return }
self.updateUI(data)
}
Memory Graph调试器的界面由三个面板组成:左侧 — 所有活动对象的列表(按类分组)及实例数量;中央 — 带有可拖拽节点的可视化图;右侧 — 所选对象或边的检查器。在对象列表中显示:类图标、内存中的实例数量、总保留大小以及占整个堆的百分比。按类名过滤支持正则表达式。
图的节点可以拖拽以提高可读性。双击节点打开有关该对象的详细信息:其所有属性及类型和值、每个属性的调用栈(回溯信息)以及保留/释放历史。回溯信息 — 关键功能:它显示哪一行代码设置了到该对象的引用。这使得无需手动检查整个代码即可找到泄漏源。
对于复杂图形,Xcode通过Layout → Hierarchical(层次布局)或Cluster(聚类布局)提供自动布局。层次布局将根对象放在上方,子对象放在下方,简化了链的查找。聚类布局将相关对象分组到簇中,当图包含多个孤立组时非常方便。根据Apple的说法,对于大多数应用程序,推荐使用层次布局 — 直观且可视化分析所需时间更少。
// Memory Graph在底层使用的LLDB命令
(lldb) script import lldb.macosx.heap
(lldb) script heap.find_variable("viewController")
0x600000c4b80: ViewController
(lldb) script heap.refs 0x600000c4b80
0x600000c4b80 -> 0x600003a4c00 (DataService)
ivar: _service, offset: 16
系统化的Memory Graph分析方法包括几个阶段。阶段1:启动应用程序,执行可能导致泄漏的场景(打开/关闭屏幕,执行网络请求)。阶段2:点击调试导航器中的Memory Graph按钮 — Xcode将构建图。阶段3:检查左侧面板中的橙色循环引用警告。阶段4:对于可疑对象,使用Show only cycles选项 — 仅显示参与循环引用的节点。
当找到循环引用时,点击循环的边并打开检查器面板。在回溯信息部分,显示设置该引用时的调用栈。例如,如果边从闭包指向self,回溯信息将显示闭包在哪个方法和哪行代码中创建。这消除了猜测的需要 — 您立即看到问题引用的创建点。根据WWDC Labs的数据,回溯信息分析将循环引用的诊断时间从15–20分钟缩短到2–3分钟。
class ProfileViewController: UIViewController {
var profileView: ProfileView!
override func viewDidLoad() {
super.viewDidLoad()
profileView = ProfileView()
// Memory Graph将在此处显示循环引用
profileView.onTap = { [unowned self] in
// ⚠️ 无主引用在nil self时可能导致崩溃
self.navigateToDetail()
}
}
func navigateToDetail() { }
}
// ✅ 正确:[weak self] + guard let self
profileView.onTap = { [weak self] in
guard let self else { return }
self.navigateToDetail()
}
Memory Graph可能显示数千个对象,使搜索变得困难。使用左侧面板中的过滤器:输入类名(例如,ProfileViewController)以仅显示该类的实例。然后选择本应被释放的实例(如果屏幕已关闭但对象仍然存在)。应用Show Reachable From — 仅显示与此对象相关的引用,隐藏图的其余部分。
有经验的开发人员不仅使用Memory Graph查找泄漏,还用于主动内存控制。检查Memory Graph每次重大架构更改后 — 添加新的委托、闭包或NotificationCenter订阅。只需执行一个典型场景并确保对象正确释放且没有循环引用即可。这需要2–3分钟,但可以防止后续数小时的调试。
内存报告在Xcode中(调试导航器选项卡)实时显示内存消耗图表。与Memory Graph一起使用:在内存消耗突然增加时打开Memory Graph。例如,在滚动带有加载图片单元格的长列表时,Memory Graph将显示哪些对象被创建,哪些被释放。如果对象数量持续增长而不减少 — 这是潜在的泄漏,在导致崩溃之前就可以看到。根据Apple的说法,从Xcode 12开始,Memory Graph + 内存报告的组合是所有iOS开发人员的推荐工作流程。
// Objective-C中通过委托泄漏的示例
@interface DownloadManager : NSObject
@property (strong) id delegate; // ❌ 应该是弱引用!
@end
@implementation DownloadManager
// Memory Graph将显示循环引用:
// ViewController → DownloadManager.delegate → ViewController
@end
// 修正:弱引用属性
@property (weak) id delegate;
特别关注闭包 — Swift中循环引用的最常见来源。当在作为对象属性存储的闭包内捕获self时,会形成经典循环。Memory Graph将其显示为一个闭包(带有{}符号的节点),通过蓝色边连接到捕获的对象。定期检查所有闭包,特别是在异步调用、GCD、Combine和SwiftUI中使用的闭包。根据Point-Free的统计,Swift项目中90%的泄漏与捕获self的闭包有关。
常见问题
Memory Graph适用于两种语言,因为它使用Objective-C运行时。与ObjC兼容的Swift对象(NSObject的子类,标记为@objc)会完整显示。没有ObjC桥接的纯Swift结构和类只能有限地看到。
对象必须在Objective-C运行时中注册。Swift值类型(结构体、枚举)不会显示。确保类继承自NSObject或使用@objc属性以便在Memory Graph中可见。
蓝色 — 强引用,持有对象。绿色 — 弱引用,不影响生命周期。灰色 — 无主引用。循环引用仅由蓝色边形成。
构建图会暂停应用程序1–3秒,并可能临时将Xcode的内存消耗增加200–500 MB。应用程序本身不会变慢,因为检查是在断点暂停期间进行的。
Xcode不支持直接导出图。使用屏幕截图进行文档记录,或使用lldb脚本heap.find_variable以编程方式提取数据。如需详细分析,请使用带堆转储的Instruments Allocations。
总结
{}节点。我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。