ios-memgraph-analysis

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

iOS Memgraph Analysis

iOS Memgraph 分析

Use memory graphs to prove why memory survives a defined lifetime boundary. Separate unreachable leaks from reachable growth, preserve raw tool output, and verify the same app-owned type and ownership path after a fix.
使用内存图来证明内存为何能在定义的生命周期边界之外存续。区分不可达泄漏与可达增长,保留原始工具输出,并验证修复后相同的应用所属类型和所有权路径。

Contents

目录

Boundary

边界

This skill owns
.memgraph
capture and command-line ownership/growth analysis. Use the Memory Graph Debugger or Instruments when their interactive graph and allocation timeline are the primary task. Use source review for a suspected closure capture only after runtime evidence identifies the lifetime or path.
本技能负责
.memgraph
文件的捕获和命令行所有权/增长分析。当交互式图和分配时间线为主要任务时,请使用Memory Graph Debugger或Instruments。仅当运行时证据确定了生命周期或路径后,才对疑似闭包捕获进行源码审查。

Evidence Model

证据模型

Do not collapse these conditions:
  • Unreachable leak: allocated memory no longer has a path from a live root. An isolated strong cycle can be unreachable and still consume memory.
  • Reachable but abandoned state: a live root still retains objects the user flow no longer needs.
    leaks
    may correctly report zero.
  • Expected cache or pool: memory survives intentionally and must be judged by its bound, eviction behavior, and pressure response.
  • Heap regression or fragmentation: footprint grows because more/larger allocations persist or dirty pages are poorly utilized, without a leak.
Apple's leak scanner uses conservative pointer discovery and incomplete type metadata. Counts can fluctuate, and a zero result does not prove the absence of an ownership bug. Strong evidence identifies the expected lifetime, an app-owned type or allocation, and a credible path or isolated reproduction.
请勿混淆以下情况:
  • 不可达泄漏:已分配内存不再有从活动根节点出发的路径。孤立的强引用循环可能处于不可达状态但仍占用内存。
  • 可达但已废弃状态:活动根节点仍保留用户流程不再需要的对象。
    leaks
    工具可能会正确报告零泄漏。
  • 预期缓存或池:内存有意存续,需根据其边界、驱逐行为和压力响应进行判断。
  • 堆回归或碎片化:内存占用增长是因为更多/更大的分配持续存在,或脏页利用率低下,但不存在泄漏。
Apple的泄漏扫描器使用保守的指针发现机制和不完整的类型元数据。计数可能会波动,零结果并不证明不存在所有权问题。有力的证据应明确预期生命周期、应用所属类型或分配,以及可信的路径或孤立复现场景。

Workflow

工作流程

1. Define the lifetime before capturing

1. 捕获前定义生命周期

Name the object that should disappear and the event that ends its useful life. For example:
EditorViewModel
should deinitialize after dismissing the editor and completing pending save work.
Record one deterministic sequence:
  1. launch or restore a known state;
  2. take an optional baseline graph;
  3. perform the feature flow;
  4. cross the expected release boundary;
  5. wait for legitimate asynchronous cleanup;
  6. take the post-flow graph.
Keep build, simulator/device, data, Malloc Stack Logging setting, and repetition count stable. Malloc Stack Logging adds valuable allocation backtraces but also overhead; compare only runs with the same setting.
命名应消失的对象以及结束其有效生命周期的事件。例如:
EditorViewModel
应在关闭编辑器并完成待处理保存工作后初始化。
记录一个确定性流程:
  1. 启动或恢复已知状态;
  2. 可选:获取基准内存图;
  3. 执行功能流程;
  4. 跨越预期的释放边界;
  5. 等待合法的异步清理完成;
  6. 获取流程后的内存图。
保持构建版本、模拟器/设备、数据、Malloc Stack Logging设置和重复次数稳定。Malloc Stack Logging会添加有价值的分配回溯信息,但也会带来额外开销;仅对比设置相同的运行实例。

2. Capture a graph without guessing the process

2. 无需猜测进程即可捕获内存图

Xcode can export a graph from the Memory Graph Debugger. For a running Simulator app, use the helper from this skill:
bash
mkdir -p /tmp/myapp-memory
mkdir /tmp/myapp-memory/run-01
python3 scripts/capture_sim_memgraph.py \
  --bundle-id com.example.MyApp \
  --output-dir /tmp/myapp-memory/run-01 \
  --pretty > /tmp/myapp-memory/run-01/capture.json
The per-run
mkdir
must fail if the capture directory already exists. Use a new run name rather than mixing stale evidence with a retry.
Pass
--udid
when more than one Simulator is booted. The helper accepts only one exact launchd label and PID; zero or multiple matches are errors. It runs the host
leaks --outputGraph
command, retains stdout/stderr, and writes a manifest. Do not replace this with
pgrep | head -1
or a substring match.
Capturing suspends the process. Do not use capture latency as performance data.
Xcode可以从Memory Graph Debugger导出内存图。对于运行中的模拟器应用,使用本技能提供的辅助脚本:
bash
mkdir -p /tmp/myapp-memory
mkdir /tmp/myapp-memory/run-01
python3 scripts/capture_sim_memgraph.py \
  --bundle-id com.example.MyApp \
  --output-dir /tmp/myapp-memory/run-01 \
  --pretty > /tmp/myapp-memory/run-01/capture.json
每次运行的
mkdir
命令必须在捕获目录已存在时失败。使用新的运行名称,避免将陈旧证据与重试结果混合。
当多个模拟器启动时,传入
--udid
参数。辅助脚本仅接受一个精确的launchd标签和PID;零匹配或多匹配均视为错误。它会运行主机的
leaks --outputGraph
命令,保留标准输出/错误输出,并写入清单文件。请勿用
pgrep | head -1
或子字符串匹配替代此脚本。
捕获操作会暂停进程。请勿将捕获延迟作为性能数据。

3. Preserve raw output and build a bounded summary

3. 保留原始输出并生成有限范围的摘要

bash
MEMGRAPH=$(jq -er \
  'select(.status == "captured") | .memgraph | select(type == "string" and length > 0)' \
  /tmp/myapp-memory/run-01/capture.json)
test -s "$MEMGRAPH"
python3 scripts/summarize_memgraph.py \
  "$MEMGRAPH" \
  --artifact-dir /tmp/myapp-memory/run-01/analysis-raw \
  --app-image 'MyApp|MyFeatureKit' \
  --trace-limit 3 --group-by-type --pretty \
  > /tmp/myapp-memory/run-01/analysis.json
Read the exact graph path from the preserved capture report; do not guess a timestamped filename. The helper creates a dedicated raw-artifact directory, refuses to reuse it, runs
leaks --list
, and parses only a conservative subset of its text.
--app-image
marks candidate rows; it does not prove ownership.
--trace-limit
runs bounded
leaks --traceTree=<address>
queries. Add
--reference-tree
when aggregate root paths are more useful than individual leaked addresses. With
--group-by-type
, that reference-tree query is grouped in the same invocation. Exit statuses 0 and 1 from
leaks
remain analyzable; a primary status above 1 fails the summary, while optional-query failures are preserved and warned as unusable without discarding a valid primary summary.
Apple does not publish these text formats as stable machine schemas. Treat parse warnings as a reason to inspect the raw artifacts, not to loosen the parser until it emits a desired answer.
bash
MEMGRAPH=$(jq -er \
  'select(.status == "captured") | .memgraph | select(type == "string" and length > 0)' \
  /tmp/myapp-memory/run-01/capture.json)
test -s "$MEMGRAPH"
python3 scripts/summarize_memgraph.py \
  "$MEMGRAPH" \
  --artifact-dir /tmp/myapp-memory/run-01/analysis-raw \
  --app-image 'MyApp|MyFeatureKit' \
  --trace-limit 3 --group-by-type --pretty \
  > /tmp/myapp-memory/run-01/analysis.json
从保存的捕获报告中读取精确的内存图路径;请勿猜测带时间戳的文件名。辅助脚本会创建专用的原始工件目录,拒绝重复使用,运行
leaks --list
命令,并仅解析其文本内容的保守子集。
--app-image
标记候选行;但不证明所有权。
--trace-limit
会运行有限范围的
leaks --traceTree=<address>
查询。当聚合根路径比单个泄漏地址更有用时,添加
--reference-tree
参数。结合
--group-by-type
参数,该引用树查询会在同一调用中进行分组。
leaks
工具的退出状态0和1仍可分析;主状态大于1时会导致摘要失败,而可选查询失败会被保留并警告为不可用,但不会丢弃有效的主摘要。
Apple未将这些文本格式发布为稳定的机器模式。将解析警告视为检查原始工件的理由,而非放宽解析器直到输出预期结果。

4. Find the first actionable app-owned edge

4. 找到第一个可操作的应用所属边

Start with an app-owned leaked type or allocation stack. Inspect:
  • the leak's object graph and Malloc Stack Logging backtrace, when present;
  • a bounded
    --traceTree=<address>
    for objects that reference one address;
  • --groupByType
    to compress repeated types and reveal a retained payload;
  • --referenceTree
    for a top-down view when the responsible address is unclear;
  • source code for the first strong edge controlled by the app.
An unreachable self-cycle may have no live root in
traceTree
. Use the grouped leak graph plus source verification or reduce the behavior to an isolated reproduction. Never invent a root path that the graph does not contain.
从应用所属的泄漏类型或分配栈开始。检查:
  • 泄漏的对象图和Malloc Stack Logging回溯(如果存在);
  • 针对引用某一地址的对象的有限范围
    --traceTree=<address>
    查询;
  • --groupByType
    参数,用于压缩重复类型并显示保留的负载;
  • 当责任地址不明确时,使用
    --referenceTree
    获取自上而下的视图;
  • 应用控制的第一个强引用边的源代码。
不可达的自循环在
traceTree
中可能没有活动根节点。使用分组泄漏图加源码验证,或将行为简化为孤立复现场景。切勿编造内存图中不存在的根路径。

5. Investigate growth when
leaks
is empty

5. 当
leaks
结果为空时调查增长情况

Use matching baseline and post-flow graphs, locate the growing region, compare object types, then trace a suspicious address back to an app-owned edge. The evidence goal is persistent reachable growth across the same lifetime—not a lower RSS value or a single large snapshot. Load reachable-growth.md only for this empty-leak branch; it contains the ordered
vmmap
,
heap
,
leaks
, and
malloc_history
queries and their logging-dependent alternatives.
使用匹配的基准和流程后内存图,定位增长区域,对比对象类型,然后将可疑地址追溯到应用所属边。证据目标是同一生命周期内的持续性可达增长——而非更低的RSS值或单个大快照。仅在这种空泄漏分支下加载reachable-growth.md;它包含有序的
vmmap
heap
leaks
malloc_history
查询及其依赖日志的替代方案。

6. Fix and verify the same lifetime

6. 修复并验证相同生命周期

Prefer the narrowest ownership correction: break the unintended strong edge, cancel work that owns the object, remove an observer, bound/evict a cache, or release a large buffer after its last use. Use
weak
when the reference may legitimately become
nil
; use
unowned
only with a proven lifetime guarantee.
Repeat the identical flow. A fix is supported when the same app-owned type/path disappears or the pre/post growth attributable to it is removed across repeated runs. Lower RSS, a smaller graph file, or a lower aggregate leak count alone is not proof.
优先选择最窄范围的所有权修正:打破意外的强引用边,取消拥有该对象的任务,移除观察者,限制/驱逐缓存,或在大型缓冲区最后一次使用后释放它。当引用可能合法变为
nil
时使用
weak
;仅在有明确生命周期保证时使用
unowned
重复完全相同的流程。当相同的应用所属类型/路径消失,或其导致的修复前后增长在多次运行中被消除时,修复才被确认有效。仅更低的RSS、更小的内存图文件或更低的总泄漏计数不足以作为证据。

Ownership Decisions

所有权决策

EvidenceNext action
App type in a root cycleInspect both strong edges and allocation stack.
No root for a leaked addressInspect grouped cycle evidence and isolate the flow.
Live root retains dismissed feature stateFollow the path to the first app-owned edge.
Zero leaks but repeated malloc growthDiff baseline/post heap objects.
Framework object dominatesFind the app-created owner, input, or call frequency.
Growth stabilizes at a documented boundTest eviction/pressure behavior before changing it.
证据下一步操作
根循环中的应用类型检查强引用边和分配栈。
泄漏地址无根节点检查分组循环证据并隔离流程。
活动根节点保留已关闭的功能状态追溯路径至第一个应用所属边。
零泄漏但重复malloc增长对比基准/流程后的堆对象。
框架对象占主导找到应用创建的所有者、输入或调用频率。
增长稳定在文档化的边界在修改前测试驱逐/压力行为。

Common Mistakes

常见错误

  • Declaring the app leak-free because
    leaks
    returned zero once.
  • Selecting the first PID or Simulator from an ambiguous list.
  • Enabling Malloc Stack Logging in only one side of a comparison.
  • Treating a parser's best-effort type column as an API guarantee.
  • Pasting enormous reference trees into a report without finding an app edge.
  • Fixing every closure with
    [weak self]
    without reasoning about lifetime.
  • Claiming success from graph size, RSS, or total-count changes without proving the target lifetime and ownership path.
  • leaks
    一次返回零就宣称应用无泄漏。
  • 从不明确的列表中选择第一个PID或模拟器。
  • 仅在对比的一侧启用Malloc Stack Logging。
  • 将解析器的最佳类型列视为API保证。
  • 在未找到应用边的情况下,将巨大的引用树粘贴到报告中。
  • 不考虑生命周期就用
    [weak self]
    修复所有闭包。
  • 在未证明目标生命周期和所有权路径的情况下,仅凭内存图大小、RSS或总数变化宣称成功。

Review Checklist

审核清单

  • The object and expected release boundary are explicit.
  • Baseline and post-flow graphs use the same build, runtime target, data state, deterministic flow, cleanup wait, repetitions, and Malloc Stack Logging setting.
  • Simulator, bundle identifier, process label, and PID are unambiguous.
  • Original graph and raw command outputs are preserved.
  • Current installed-tool help confirms version-sensitive command shapes.
  • Leak, reachable growth, expected cache, and fragmentation are separated.
  • The finding names an app-owned type/allocation and credible path.
  • Missing type metadata or conservative-scanner limits are disclosed.
  • The fix changes one ownership/lifetime cause.
  • Verification repeats the same flow and evidence query.
  • 对象和预期释放边界明确。
  • 基准和流程后内存图使用相同的构建版本、运行时目标、数据状态、确定性流程、清理等待时间、重复次数和Malloc Stack Logging设置。
  • 模拟器、Bundle ID、进程标签和PID明确无误。
  • 原始内存图和原始命令输出已保留。
  • 当前安装的工具帮助文档确认了版本敏感的命令格式。
  • 泄漏、可达增长、预期缓存和碎片化已区分。
  • 发现结果命名了应用所属类型/分配和可信路径。
  • 已披露缺失的类型元数据或保守扫描器限制。
  • 修复仅更改了一个所有权/生命周期原因。
  • 验证重复了相同的流程和证据查询。

References

参考资料