axiom-testflight-triage
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTestFlight Crash & Feedback Triage
TestFlight崩溃与反馈处理流程
Overview
概述
Systematic workflow for investigating TestFlight crashes and reviewing beta feedback using Xcode Organizer. Core principle: Understand the crash before writing any fix — 15 minutes of triage prevents hours of debugging.
使用Xcode Organizer调查TestFlight崩溃并审核Beta版本反馈的系统化工作流。核心原则: 在编写任何修复之前先理解崩溃原因——15分钟的处理工作能避免数小时的调试。
Red Flags — Use This Skill When
适用场景
- "A beta tester said my app crashed"
- "I see crashes in App Store Connect metrics but don't know how to investigate"
- "Crash logs in Organizer aren't symbolicated"
- "User sent a screenshot of a crash but I can't reproduce it"
- "App was killed but there's no crash — just disappeared"
- "TestFlight feedback has screenshots I need to review"
- "有Beta测试人员说我的应用崩溃了"
- "我在App Store Connect指标中看到崩溃,但不知道如何调查"
- "Organizer中的崩溃日志未被符号化"
- "用户发送了崩溃截图,但我无法复现问题"
- "应用直接消失了,没有崩溃报告"
- "我需要审核TestFlight反馈中的截图"
Decision Tree — Start Here
决策树——从这里开始
"A user reported a crash"
"用户报告了崩溃"
- Open Xcode Organizer (Window → Organizer → Crashes tab)
- Select your app from the left sidebar
- Find the build version the user was running
- Is the crash symbolicated?
- YES (you see function names) → Go to Reading the Crash Report
- NO (you see hex addresses like ) → Go to Symbolication Workflow
0x100abc123
- Can you identify the crash location?
- YES → Go to Common Crash Patterns
- NO → Go to Claude-Assisted Interpretation
- 打开Xcode Organizer(窗口 → Organizer → 崩溃标签页)
- 从左侧边栏选择你的应用
- 找到用户正在使用的构建版本
- 崩溃日志是否已符号化?
- 你能确定崩溃位置吗?
- 是 → 前往常见崩溃模式
- 否 → 前往Claude辅助解读
"App was killed but no crash report"
"应用被终止但无崩溃报告"
Not all terminations produce crash reports. Check for:
- Jetsam reports — System killed app due to memory pressure
- Organizer shows these separately from crashes
- Look for high value
pageOuts
- Watchdog termination — Main thread blocked too long
- Exception code ("ate bad food")
0x8badf00d - Happens during launch (>20s) or background tasks (>10s)
- Exception code
- MetricKit diagnostics — On-device termination reasons
- Requires MetricKit integration in your app
并非所有终止都会生成崩溃报告,请检查以下情况:
- Jetsam报告 — 系统因内存压力终止应用
- Organizer会将这些报告与崩溃分开显示
- 查看较高的值
pageOuts
- Watchdog终止 — 主线程被阻塞时间过长
- 异常代码(谐音“ate bad food”)
0x8badf00d - 通常发生在启动阶段(超过20秒)或后台任务(超过10秒)
- 异常代码
- MetricKit诊断信息 — 设备端的终止原因
- 需要在应用中集成MetricKit
→ 查看无崩溃报告的终止情况
"I want to review TestFlight feedback"
"我想审核TestFlight反馈"
- Xcode Organizer → Feedback tab (next to Crashes)
- Or App Store Connect → My Apps → [App] → TestFlight → Feedback
→ See Feedback Triage Workflow
Xcode Organizer Walkthrough
Xcode Organizer操作指南
Opening the Organizer
打开Organizer
Window → Organizer (or ⌘⇧O from Xcode)
窗口 → Organizer(或在Xcode中使用快捷键⌘⇧O)
UI Layout
UI布局
┌─────────────────────────────────────────────────────────────────┐
│ [Toolbar: Time Period ▼] [Version ▼] [Product ▼] [Release ▼] │
├──────────┬──────────────────────────┬───────────────────────────┤
│ Sidebar │ Crashes List │ Inspector │
│ │ │ │
│ • Crashes│ ┌─────────────────────┐ │ Distribution Graph │
│ • Energy │ │ syncFavorites crash │ │ ┌─────────────────────┐ │
│ • Hang │ │ 21 devices • 7 today│ │ │ ▄ ▄▄▄ v2.0 │ │
│ • Disk │ └─────────────────────┘ │ │ ▄▄▄▄▄ v2.0.1 │ │
│ Feedback │ ┌─────────────────────┐ │ └─────────────────────┘ │
│ │ │ Another crash... │ │ │
│ │ └─────────────────────┘ │ Device Distribution │
│ │ │ OS Distribution │
│ ├──────────────────────────┤ │
│ │ Log View │ [Feedback Inspector] │
│ │ (simplified crash view) │ Shows tester feedback │
│ │ │ for selected crash │
└──────────┴──────────────────────────┴───────────────────────────┘┌─────────────────────────────────────────────────────────────────┐
│ [工具栏: 时间范围 ▼] [版本 ▼] [产品 ▼] [发布渠道 ▼] │
├──────────┬──────────────────────────┬───────────────────────────┤
│ 侧边栏 │ 崩溃列表 │ 检查器 │
│ │ │ │
│ • 崩溃│ ┌─────────────────────┐ │ 分布图表 │
│ • 能耗 │ │ syncFavorites崩溃 │ │ ┌─────────────────────┐ │
│ • 卡顿 │ │ 21台设备 • 今日7次│ │ │ ▄ ▄▄▄ v2.0 │ │
│ • 磁盘 │ └─────────────────────┘ │ │ ▄▄▄▄▄ v2.0.1 │ │
│ 反馈 │ ┌─────────────────────┐ │ └─────────────────────┘ │
│ │ │ 另一个崩溃... │ │ │
│ │ └─────────────────────┘ │ 设备分布 │
│ │ │ 系统版本分布 │
│ ├──────────────────────────┤ │
│ │ 日志视图 │ [反馈检查器] │
│ │ (简化的崩溃视图) │ 显示所选崩溃对应的测试人员反馈 │
│ │ │ │
└──────────┴──────────────────────────┴───────────────────────────┘Key Features
核心功能
| Feature | What It Does |
|---|---|
| Speedy Delivery | TestFlight crashes delivered moments after occurrence (not daily) |
| Year of History | Filter crashes by time period, see monthly trends |
| Product Filter | Filter by App Clip, watch app, extensions, or main app |
| Version Filter | Drill down to specific builds |
| Release Filter | Separate TestFlight vs App Store crashes |
| Share Button | Share crash link with team members |
| Feedback Inspector | See tester comments for selected crash |
| 功能 | 说明 |
|---|---|
| 快速交付 | TestFlight崩溃会在发生后立即上报(而非每日汇总) |
| 一年历史记录 | 可按时间段筛选崩溃,查看月度趋势 |
| 产品筛选 | 可按App Clip、手表应用、扩展或主应用筛选 |
| 版本筛选 | 深入查看特定构建版本的崩溃情况 |
| 发布渠道筛选 | 区分TestFlight和App Store的崩溃 |
| 分享按钮 | 与团队成员分享崩溃链接 |
| 反馈检查器 | 查看所选崩溃对应的测试人员评论 |
Crash Entry Badges
崩溃条目标识
Crashes in the list show badges indicating origin:
| Badge | Meaning |
|---|---|
| App Clip | Crash from your App Clip |
| Watch | Crash from watchOS companion |
| Extension | Crash from share extension, widget, etc. |
| (none) | Main iOS app |
崩溃列表中的标识会显示崩溃来源:
| 标识 | 含义 |
|---|---|
| App Clip | 来自App Clip的崩溃 |
| Watch | 来自watchOS配套应用的崩溃 |
| Extension | 来自分享扩展、小组件等的崩溃 |
| 无标识 | 来自主iOS应用的崩溃 |
The Triage Questions Workflow
处理流程提问环节
Before diving into code, ask yourself these questions (from WWDC):
在深入代码之前,请先问自己以下问题(来自WWDC):
Question 1 — How Long Has This Been an Issue
问题1:这个问题存在多久了
→ Check the inspector's graph area on the right
→ Graph legend shows which versions are affected
→ Look for when the crash first appeared
→ 查看右侧检查器中的图表区域
→ 图表图例显示受影响的版本
→ 查看崩溃首次出现的时间
Question 2 — Is This Affecting Production or Just TestFlight
问题2:这会影响正式版还是仅影响TestFlight版本
→ Use the Release filter in toolbar
→ Select "Release" to see App Store crashes only
→ Select "TestFlight" for beta crashes only
→ 使用工具栏中的发布渠道筛选
→ 选择"Release"仅查看App Store崩溃
→ 选择"TestFlight"仅查看Beta版本崩溃
Question 3 — What Was the User Doing
问题3:用户当时在做什么
→ Open the Feedback Inspector (right panel)
→ Check for tester comments describing their actions
→ Context clues: network state, battery level, disk space
→ 打开反馈检查器(右侧面板)
→ 查看测试人员描述的操作
→ 上下文线索:网络状态、电池电量、磁盘空间
Using the Feedback Inspector
使用反馈检查器
When a crash has associated TestFlight feedback, you'll see a feedback icon in the crashes list. Click it to open the Feedback Inspector.
Each feedback entry shows:
| Field | Why It Matters |
|---|---|
| Version/Build | Confirms exact build tester was running |
| Device model | Device-specific crashes (older devices, specific screen sizes) |
| Battery level | Low battery can affect app behavior |
| Available disk | Low disk can cause write failures |
| Network type | Cellular vs WiFi, connectivity issues |
| Tester comment | Their description of what happened |
Example insight from WWDC: A tester commented "I was going through a tunnel and hit the favorite button. A few seconds later, it crashed." This revealed a network timeout issue — the crash occurred because a 10-second timeout was too short for poor network conditions.
当崩溃关联了TestFlight反馈时,崩溃列表中会显示反馈图标。点击即可打开反馈检查器。
每个反馈条目包含:
| 字段 | 重要性 |
|---|---|
| 版本/构建号 | 确认测试人员使用的具体构建版本 |
| 设备型号 | 特定设备的崩溃问题(旧设备、特定屏幕尺寸) |
| 电池电量 | 低电量会影响应用行为 |
| 可用磁盘空间 | 磁盘空间不足会导致写入失败 |
| 网络类型 | 蜂窝网络vs WiFi,连接问题 |
| 测试人员评论 | 他们对问题的描述 |
WWDC示例洞察: 有测试人员评论"我在隧道里点击了收藏按钮,几秒后应用崩溃了"。这揭示了网络超时问题——崩溃是因为10秒的超时时间在网络条件差的情况下太短。
Opening Crash in Project
在项目中打开崩溃
- Select a crash in the list
- Click Open in Project button
- Xcode opens with:
- Debug Navigator showing backtrace
- Editor highlighting the exact crash line
- 在列表中选择一个崩溃
- 点击在项目中打开按钮
- Xcode会打开:
- 显示调用栈的调试导航器
- 编辑器高亮显示崩溃的具体代码行
Sharing Crashes
分享崩溃信息
- Select a crash
- Click Share button in toolbar
- Options:
- Copy link to share with team
- Add to your to-do list
- When teammate clicks link, Organizer opens focused on that specific crash
- 选择一个崩溃
- 点击工具栏中的分享按钮
- 选项:
- 复制链接与团队成员分享
- 添加到你的待办事项
- 当队友点击链接时,Organizer会直接打开该特定崩溃的详情
Symbolication Workflow
符号化工作流
Why Crashes Aren't Symbolicated
崩溃未被符号化的原因
Crash reports show raw memory addresses until matched with dSYM files (debug symbol files). Xcode handles this automatically when:
- You archived the build in Xcode (not command-line only)
- "Upload symbols to Apple" was enabled during distribution
- The dSYM is indexed by Spotlight
崩溃报告会显示原始内存地址,直到与dSYM文件(调试符号文件)匹配。当满足以下条件时,Xcode会自动处理符号化:
- 你使用Xcode归档了构建版本(而非仅使用命令行)
- 分发时启用了"Upload symbols to Apple"
- dSYM文件已被Spotlight索引
Quick Check: Is It Symbolicated?
快速检查:是否已符号化
In Organizer, look at the stack trace:
| What You See | Status |
|---|---|
| Unsymbolicated — needs dSYM |
| Symbolicated — ready to analyze |
| System frames symbolicated, app frames not | Partially symbolicated — missing your dSYM |
在Organizer中查看调用栈:
| 显示内容 | 状态 |
|---|---|
| 未符号化 — 需要dSYM文件 |
| 已符号化 — 可分析 |
| 系统框架已符号化,应用框架未符号化 | 部分符号化 — 缺少你的dSYM文件 |
Manual Symbolication
手动符号化
If automatic symbolication failed:
bash
undefined如果自动符号化失败:
bash
undefined1. Find the crash's build UUID (shown in crash report header)
1. 查找崩溃的构建UUID(在崩溃报告头部显示)
Look for "Binary Images" section, find your app's UUID
查看"Binary Images"部分,找到你的应用UUID
2. Find matching dSYM
2. 查找匹配的dSYM文件
mdfind "com_apple_xcode_dsym_uuids == YOUR-UUID-HERE"
mdfind "com_apple_xcode_dsym_uuids == YOUR-UUID-HERE"
3. If not found, check Archives
3. 如果未找到,检查归档目录
ls ~/Library/Developer/Xcode/Archives/
ls ~/Library/Developer/Xcode/Archives/
4. Symbolicate a specific address
4. 对特定地址进行符号化
xcrun atos -arch arm64
-o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp
-l 0x100000000
0x0000000100abc123
-o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp
-l 0x100000000
0x0000000100abc123
undefinedxcrun atos -arch arm64
-o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp
-l 0x100000000
0x0000000100abc123
-o MyApp.app.dSYM/Contents/Resources/DWARF/MyApp
-l 0x100000000
0x0000000100abc123
undefinedCommon Symbolication Failures
常见符号化失败情况
| Symptom | Cause | Fix |
|---|---|---|
| System frames OK, app frames hex | Missing dSYM for your app | Find dSYM in Archives folder, or re-archive with symbols |
| Nothing symbolicated | UUID mismatch between crash and dSYM | Verify UUIDs match; rebuild exact same commit |
| "No such file" from atos | dSYM not in Spotlight index | Run |
| Can't find dSYM anywhere | Archived without symbols | Enable "Debug Information Format = DWARF with dSYM" in build settings |
| 症状 | 原因 | 修复方案 |
|---|---|---|
| 系统框架正常,应用框架显示十六进制地址 | 缺少应用的dSYM文件 | 在归档目录中查找dSYM文件,或重新归档并包含符号 |
| 完全未符号化 | 崩溃报告与dSYM文件的UUID不匹配 | 验证UUID是否匹配;重新构建完全相同的提交版本 |
| atos命令提示"No such file" | dSYM文件未在Spotlight索引中 | 运行 |
| 完全找不到dSYM文件 | 归档时未包含符号 | 在构建设置中启用"Debug Information Format = DWARF with dSYM" |
Preventing Symbolication Issues
避免符号化问题
bash
undefinedbash
undefinedVerify dSYM exists after archive
归档后验证dSYM文件是否存在
ls ~/Library/Developer/Xcode/Archives/YYYY-MM-DD/MyApp*.xcarchive/dSYMs/
ls ~/Library/Developer/Xcode/Archives/YYYY-MM-DD/MyApp*.xcarchive/dSYMs/
Verify UUID matches
验证UUID是否匹配
dwarfdump --uuid MyApp.app.dSYM
---dwarfdump --uuid MyApp.app.dSYM
---Reading the Crash Report
阅读崩溃报告
Key Fields (What Actually Matters)
关键字段(核心信息)
| Field | What It Tells You |
|---|---|
| Exception Type | Category of crash (EXC_BAD_ACCESS, EXC_CRASH, etc.) |
| Exception Codes | Specific error (KERN_INVALID_ADDRESS = null pointer) |
| Termination Reason | Why the system killed the process |
| Crashed Thread | Which thread died (Thread 0 = main thread) |
| Application Specific Information | Often contains the actual error message |
| Binary Images | Loaded frameworks (helps identify third-party culprits) |
| 字段 | 说明 |
|---|---|
| Exception Type | 崩溃类型(EXC_BAD_ACCESS、EXC_CRASH等) |
| Exception Codes | 具体错误代码(KERN_INVALID_ADDRESS = 空指针) |
| Termination Reason | 系统终止进程的原因 |
| Crashed Thread | 崩溃的线程(Thread 0 = 主线程) |
| Application Specific Information | 通常包含实际错误消息 |
| Binary Images | 已加载的框架(帮助识别第三方库问题) |
Reading the Stack Trace
阅读调用栈
The crashed thread's stack trace reads top to bottom:
- Frame 0 = Where the crash occurred (most specific)
- Lower frames = What called it (call chain)
- Look for your code = Frames with your app/framework name
Thread 0 Crashed:
0 libsystem_kernel.dylib __pthread_kill + 8 ← System code
1 libsystem_pthread.dylib pthread_kill + 288 ← System code
2 libsystem_c.dylib abort + 128 ← System code
3 MyApp ViewController.loadData() ← YOUR CODE (start here)
4 MyApp ViewController.viewDidLoad()
5 UIKitCore -[UIViewController _loadView]Start at frame 3 — the first frame in your code. Work down to understand the call chain.
崩溃线程的调用栈是从上到下读取的:
- Frame 0 = 崩溃发生的位置(最具体)
- 下方的帧 = 调用该代码的上层逻辑(调用链)
- 寻找你的代码 = 包含你的应用/框架名称的帧
Thread 0 Crashed:
0 libsystem_kernel.dylib __pthread_kill + 8 ← 系统代码
1 libsystem_pthread.dylib pthread_kill + 288 ← 系统代码
2 libsystem_c.dylib abort + 128 ← 系统代码
3 MyApp ViewController.loadData() ← 你的代码(从此处开始分析)
4 MyApp ViewController.viewDidLoad()
5 UIKitCore -[UIViewController _loadView]从Frame 3开始分析 — 这是你的代码中的第一帧。向下查看以理解调用链。
Example: Interpreting a Real Crash
示例:解读真实崩溃报告
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000010
Thread 0 Crashed:
0 MyApp 0x100abc123 UserManager.currentUser.getter + 45
1 MyApp 0x100abc456 ProfileViewController.viewDidLoad() + 123
2 UIKitCore 0x1a2b3c4d5 -[UIViewController loadView] + 89Translation:
- with
EXC_BAD_ACCESS= Tried to access invalid memoryKERN_INVALID_ADDRESS - Address = Very low address, almost certainly nil dereference
0x10 - Crashed in = Accessing a property that was nil
currentUser.getter - Called from = During view setup
ProfileViewController.viewDidLoad()
Likely cause: Force-unwrapping an optional that was nil, or accessing a deallocated object.
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x0000000000000010
Thread 0 Crashed:
0 MyApp 0x100abc123 UserManager.currentUser.getter + 45
1 MyApp 0x100abc456 ProfileViewController.viewDidLoad() + 123
2 UIKitCore 0x1a2b3c4d5 -[UIViewController loadView] + 89解读:
- 伴随
EXC_BAD_ACCESS= 尝试访问无效内存KERN_INVALID_ADDRESS - 地址= 地址值极低,几乎可以肯定是空指针引用
0x10 - 在中崩溃 = 访问了一个为nil的属性
currentUser.getter - 从调用 = 在视图初始化阶段发生
ProfileViewController.viewDidLoad()
可能原因: 强制解包了一个为nil的可选值,或访问了已释放的对象。
Common Crash Patterns
常见崩溃模式
EXC_BAD_ACCESS (SIGSEGV / SIGBUS)
EXC_BAD_ACCESS (SIGSEGV / SIGBUS)
What it means: Accessed memory that doesn't belong to you.
Common causes in Swift:
| Pattern | Example | Fix |
|---|---|---|
| Force-unwrap nil | | Use |
| Deallocated object | Accessing | Use |
| Array out of bounds | | Check bounds first |
| Uninitialized pointer | C interop with bad pointer | Validate pointer before use |
swift
// Before (crashes if user is nil)
let name = user!.name
// After (safe)
guard let user = user else {
logger.warning("User was nil in ProfileViewController")
return
}
let name = user.name含义: 访问了不属于你的内存。
Swift中的常见原因:
| 模式 | 示例 | 修复方案 |
|---|---|---|
| 强制解包nil | | 使用 |
| 访问已释放对象 | 在闭包逃逸后访问已释放的 | 使用 |
| 数组越界 | | 先检查边界 |
| 未初始化指针 | C语言交互时使用了无效指针 | 使用前验证指针有效性 |
swift
// 修复前(当user为nil时会崩溃)
let name = user!.name
// 修复后(安全)
guard let user = user else {
logger.warning("ProfileViewController中的User为nil")
return
}
let name = user.nameEXC_CRASH (SIGABRT)
EXC_CRASH (SIGABRT)
What it means: App deliberately terminated itself.
Common causes:
| Pattern | Clue in Crash Report |
|---|---|
| Your assertion message in Application Specific Info |
| Uncaught Objective-C exception | |
| Swift runtime error | "Fatal error: ..." message |
| Deadlock detected | |
Debug tip: Look at "Application Specific Information" section — it usually contains the actual error message.
含义: 应用主动终止自己。
常见原因:
| 模式 | 崩溃报告中的线索 |
|---|---|
| 应用特定信息中包含你的断言消息 |
| 未捕获的Objective-C异常 | 报告中包含 |
| Swift运行时错误 | 包含"Fatal error: ..."消息 |
| 检测到死锁 | |
调试技巧: 查看"Application Specific Information"部分——通常包含实际错误消息。
Watchdog Termination (0x8badf00d)
Watchdog终止 (0x8badf00d)
What it means: Main thread was blocked too long and the system killed your app.
Time limits:
| Context | Limit |
|---|---|
| App launch | ~20 seconds |
| Background task | ~10 seconds |
| App going to background | ~5 seconds |
Common causes:
- Synchronous network request on main thread
- Synchronous file I/O on main thread
- Deadlock between queues
- Expensive computation blocking UI
swift
// Before (blocks main thread — will trigger watchdog)
let data = try Data(contentsOf: largeFileURL)
processData(data)
// After (offload to background)
Task.detached {
let data = try Data(contentsOf: largeFileURL)
await MainActor.run {
self.processData(data)
}
}含义: 主线程被阻塞时间过长,系统终止了应用。
时间限制:
| 场景 | 限制 |
|---|---|
| 应用启动 | ~20秒 |
| 后台任务 | ~10秒 |
| 应用进入后台 | ~5秒 |
常见原因:
- 在主线程执行同步网络请求
- 在主线程执行同步文件I/O
- 队列之间的死锁
- 耗时计算阻塞UI
swift
// 修复前(阻塞主线程——会触发Watchdog)
let data = try Data(contentsOf: largeFileURL)
processData(data)
// 修复后(转移到后台线程)
Task.detached {
let data = try Data(contentsOf: largeFileURL)
await MainActor.run {
self.processData(data)
}
}Jetsam (Memory Pressure Kill)
Jetsam(内存压力终止)
What it means: System terminated your app to free memory. No crash report — just gone.
Symptoms:
- App "disappears" without any crash
- Jetsam report in Organizer (separate from crashes)
- High value in report
pageOuts - Often happens during photo/video processing or large data operations
Investigation:
- Profile with Instruments → Allocations
- Look for memory spikes during the reported operation
- Check for image caching without size limits
- Look for large data structures kept in memory
Common fixes:
- Use for batch processing
autoreleasepool - Implement image cache with memory limits
- Stream large files instead of loading entirely
- Release references to large objects when backgrounded
含义: 系统为释放内存终止了应用。无崩溃报告——应用直接消失。
症状:
- 应用"消失"但无任何崩溃报告
- Organizer中有Jetsam报告(与崩溃分开显示)
- 报告中值较高
pageOuts - 通常发生在照片/视频处理或大数据操作期间
调查方法:
- 使用Instruments的Allocations工具分析
- 查看报告操作期间的内存峰值
- 检查是否有无限大小的图片缓存
- 检查是否有长时间保存在内存中的大数据结构
常见修复方案:
- 批量处理时使用
autoreleasepool - 实现带内存限制的图片缓存
- 流式处理大文件而非一次性加载
- 进入后台时释放大对象的引用
Terminations Without Crash Reports
无崩溃报告的终止情况
When users report "the app just closed" but you find no crash:
当用户报告"应用直接关闭了"但你找不到任何崩溃报告时:
The Terminations Organizer
终止情况Organizer
The Terminations Organizer (separate from Crashes) shows trends of app terminations that aren't programming crashes:
Window → Organizer → Terminations (in sidebar)
| Termination Category | What It Means |
|---|---|
| Launch timeout | App took too long to launch |
| Memory limit | Hit system memory ceiling |
| CPU limit (background) | Too much CPU while backgrounded |
| Background task timeout | Background task exceeded time limit |
Key insight: Compare termination rates against previous versions to find regressions. A spike in memory terminations after a release indicates a memory leak or increased footprint.
终止情况Organizer(与崩溃分开)显示非编程崩溃的应用终止趋势:
窗口 → Organizer → 终止情况(在侧边栏中)
| 终止类别 | 含义 |
|---|---|
| 启动超时 | 应用启动时间过长 |
| 内存限制 | 达到系统内存上限 |
| CPU限制(后台) | 后台时CPU占用过高 |
| 后台任务超时 | 后台任务超过时间限制 |
关键洞察: 对比不同版本的终止率以找到回归问题。新版本发布后内存终止率飙升,表明存在内存泄漏或内存占用增加。
Check for Jetsam
检查Jetsam报告
- Organizer → Select app → Look for "Disk Write Diagnostics" or "Hang Diagnostics"
- These aren't crashes but system-initiated terminations
- Organizer → 选择应用 → 查看"磁盘写入诊断"或"卡顿诊断"
- 这些不是崩溃,而是系统发起的终止
Check for Background Termination
检查后台终止
Apps can be terminated in background for:
- Memory pressure (jetsam)
- CPU usage while backgrounded
- Background task timeout
应用在后台可能因以下原因被终止:
- 内存压力(Jetsam)
- 后台CPU占用过高
- 后台任务超时
Ask the User
询问用户
If no reports exist:
- "Was the app in the foreground when it closed?"
- "Did you see any error message?"
- "What were you doing right before it happened?"
- "How long had the app been open?"
如果没有相关报告:
- "应用关闭时是在前台吗?"
- "你看到任何错误提示了吗?"
- "发生前你正在做什么?"
- "应用已经打开多久了?"
Enable Better Diagnostics with MetricKit
使用MetricKit启用更好的诊断
MetricKit crash diagnostics are now delivered on the next app launch (not aggregated daily). This gives you faster access to crash data.
swift
import MetricKit
class MetricsManager: NSObject, MXMetricManagerSubscriber {
static let shared = MetricsManager()
func startListening() {
MXMetricManager.shared.add(self)
}
func didReceive(_ payloads: [MXMetricPayload]) {
// Process performance metrics
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
// Crash diagnostics — delivered on next launch
if let crashDiagnostics = payload.crashDiagnostics {
for crash in crashDiagnostics {
// Process crash diagnostic
print("Crash: \(crash.callStackTree)")
}
}
// Hang diagnostics
if let hangDiagnostics = payload.hangDiagnostics {
for hang in hangDiagnostics {
print("Hang duration: \(hang.hangDuration)")
}
}
}
}
}When to use MetricKit vs Organizer:
| Use Case | Tool |
|---|---|
| Quick triage of TestFlight crashes | Organizer (faster, visual) |
| Programmatic crash analysis | MetricKit |
| Custom crash reporting integration | MetricKit |
| Termination trends across versions | Terminations Organizer |
MetricKit崩溃诊断现在会在应用下次启动时上报(而非每日汇总)。这能让你更快获取崩溃数据。
swift
import MetricKit
class MetricsManager: NSObject, MXMetricManagerSubscriber {
static let shared = MetricsManager()
func startListening() {
MXMetricManager.shared.add(self)
}
func didReceive(_ payloads: [MXMetricPayload]) {
// 处理性能指标
}
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for payload in payloads {
// 崩溃诊断——下次启动时上报
if let crashDiagnostics = payload.crashDiagnostics {
for crash in crashDiagnostics {
// 处理崩溃诊断
print("Crash: \(crash.callStackTree)")
}
}
// 卡顿诊断
if let hangDiagnostics = payload.hangDiagnostics {
for hang in hangDiagnostics {
print("Hang duration: \(hang.hangDuration)")
}
}
}
}
}MetricKit与Organizer的适用场景对比:
| 场景 | 工具 |
|---|---|
| TestFlight崩溃快速处理 | Organizer(更快、可视化) |
| 程序化崩溃分析 | MetricKit |
| 自定义崩溃报告集成 | MetricKit |
| 跨版本终止趋势分析 | 终止情况Organizer |
Claude-Assisted Interpretation
Claude辅助解读
Using the Crash Analyzer Agent
使用崩溃分析Agent
For automated crash analysis, use the crash-analyzer agent:
/axiom:analyze-crashOr trigger naturally:
- "Analyze this crash log"
- "Parse this .ips file: ~/Library/Logs/DiagnosticReports/MyApp.ips"
- "Why did my app crash? Here's the report..."
The agent will:
- Parse the crash report (JSON .ips or text .crash format)
- Check symbolication status
- Categorize by crash pattern (null pointer, Swift runtime, watchdog, jetsam, etc.)
- Generate actionable analysis with specific next steps
对于自动化崩溃分析,使用crash-analyzer Agent:
/axiom:analyze-crash或自然触发:
- "分析这个崩溃日志"
- "解析这个.ips文件: ~/Library/Logs/DiagnosticReports/MyApp.ips"
- "我的应用为什么崩溃了?这是报告..."
该Agent会:
- 解析崩溃报告(JSON .ips或文本 .crash格式)
- 检查符号化状态
- 按崩溃模式分类(空指针、Swift运行时、Watchdog、Jetsam等)
- 生成包含具体下一步操作的可执行分析结果
Effective Prompts
有效提示示例
Basic interpretation:
Here's a crash report from my iOS app. Help me understand:
1. What type of crash is this?
2. Where in my code did it crash?
3. What's the likely cause?
[paste full crash report]With context (better results):
My TestFlight app crashed. Here's what I know:
- User was [describe action, e.g., "tapping the save button"]
- iOS version: [from crash report]
- Device: [from crash report]
Crash report:
[paste full crash report]
The relevant code is in [file/class name]. Help me understand the cause.基础解读:
这是我的iOS应用的崩溃报告。帮我理解:
1. 这是什么类型的崩溃?
2. 崩溃发生在我的代码的哪个位置?
3. 可能的原因是什么?
[粘贴完整崩溃报告]带上下文(效果更好):
我的TestFlight应用崩溃了。我已知的信息:
- 用户当时正在[描述操作,例如"点击保存按钮"]
- iOS版本:[来自崩溃报告]
- 设备:[来自崩溃报告]
崩溃报告:
[粘贴完整崩溃报告]
相关代码在[文件/类名]中。帮我分析原因。What to Include
需要包含的信息
| Include | Why |
|---|---|
| Full crash report | Partial reports lose context |
| What user was doing | Helps narrow down code paths |
| Relevant code snippets | If you know the crash area |
| iOS version and device | Some crashes are device/OS specific |
| 内容 | 原因 |
|---|---|
| 完整崩溃报告 | 部分报告会丢失上下文 |
| 用户操作 | 帮助缩小代码路径范围 |
| 相关代码片段 | 如果你知道崩溃大致区域 |
| iOS版本和设备 | 部分崩溃是特定设备/系统版本的问题 |
What Claude Can Help With
Claude能提供的帮助
- Interpreting exception types and codes
- Identifying likely cause from stack trace
- Explaining unfamiliar system frames
- Suggesting where to add logging
- Proposing fix patterns
- 解读异常类型和代码
- 从调用栈识别可能的原因
- 解释不熟悉的系统帧
- 建议添加日志的位置
- 提出修复模式
What Requires Your Judgment
需要你判断的内容
- Whether the suggested fix is correct for your architecture
- How to reproduce the crash locally
- Priority relative to other bugs
- Whether it's a regression or long-standing issue
- 建议的修复是否适合你的架构
- 如何在本地复现崩溃
- 相对于其他Bug的优先级
- 这是回归问题还是长期存在的问题
Feedback Triage Workflow
反馈处理工作流
Where to Find Feedback
反馈位置
Xcode Organizer (recommended):
Window → Organizer → Select app → Feedback tab
App Store Connect:
My Apps → [App] → TestFlight → Feedback
Xcode Organizer(推荐):
窗口 → Organizer → 选择应用 → 反馈标签页
App Store Connect:
我的App → [你的应用] → TestFlight → 反馈
What's in Each Feedback Entry
每个反馈条目包含的内容
| Component | Description |
|---|---|
| Screenshot | What the user saw (often the most valuable part) |
| Text comment | User's description of the issue |
| Device/OS | iPhone model and iOS version |
| App version | Which TestFlight build |
| Timestamp | When submitted |
| 组件 | 描述 |
|---|---|
| 截图 | 用户看到的内容(通常是最有价值的部分) |
| 文字评论 | 用户对问题的描述 |
| 设备/系统版本 | iPhone型号和iOS版本 |
| 应用版本 | 对应的TestFlight构建版本 |
| 时间戳 | 提交时间 |
Triage Workflow
处理工作流
- Sort by recency — Newest first, unless investigating specific issue
- Scan screenshots — Visual issues are immediately apparent
- Read comments — User's description and context
- Check version — Is this fixed in a newer build?
- Categorize:
| Category | Action |
|---|---|
| 🐛 Bug | Investigate, file issue, prioritize fix |
| 💡 Feature request | Add to backlog if valuable |
| ❓ Unclear | Can't act without more context |
| ✅ Working as intended | May indicate UX confusion |
- 按时间排序 — 最新的优先,除非正在调查特定问题
- 扫描截图 — 视觉问题能立即识别
- 阅读评论 — 用户的描述和上下文
- 检查版本 — 新版本中是否已修复?
- 分类:
| 分类 | 操作 |
|---|---|
| 🐛 Bug | 调查、创建Issue、确定修复优先级 |
| 💡 功能请求 | 如果有价值则添加到待办列表 |
| ❓ 不明确 | 缺少更多上下文无法处理 |
| ✅ 符合预期 | 可能表明存在UX混淆 |
Limitations
局限性
- No direct reply — TestFlight doesn't support responding to feedback
- Screenshots only — No video recordings
- Limited context — Users often don't explain what they were trying to do
- 无法直接回复 — TestFlight不支持回复反馈
- 仅支持截图 — 不支持视频录制
- 上下文有限 — 用户通常不会解释他们的操作意图
Getting More Context
获取更多上下文
If feedback is unclear and the tester is reachable:
- Contact through TestFlight group email
- Add in-app feedback mechanism with more detail capture
- Include reproduction steps prompt in your TestFlight notes
如果反馈不明确且能联系到测试人员:
- 通过TestFlight群组邮件联系
- 添加应用内反馈机制以收集更多细节
- 在TestFlight说明中提示用户提供复现步骤
Pressure Scenarios
压力场景
Scenario 1: "VIP user says app crashes constantly, but I can't find any crash reports"
场景1:"重要用户说应用经常崩溃,但我找不到任何崩溃报告"
Pressure: Important stakeholder, no evidence, tempted to dismiss with "works for me"
Correct approach:
- Verify they're on TestFlight (not App Store, not dev build)
- Confirm they've consented to share diagnostics (Settings → Privacy → Analytics)
- Check for jetsam reports (kills without crash reports)
- Check crash reports for their specific device/OS combination
- Ask for specific reproduction steps
- If still nothing: request screen recording of the issue
Response template:
"I've checked our crash reports and don't see crashes matching your description yet. To help investigate: (1) Could you confirm you're running the TestFlight version? (2) What exactly happens — does the app close suddenly, freeze, or show an error? (3) What were you doing right before? This will help me find the issue."
Why this matters: "Works for me" destroys trust. Investigate thoroughly before dismissing.
压力: 重要利益相关者,无证据,容易想用"我这里正常"来敷衍
正确做法:
- 确认他们使用的是TestFlight版本(而非App Store或开发版本)
- 确认他们已同意共享诊断信息(设置 → 隐私与安全性 → 分析与改进)
- 检查Jetsam报告(无崩溃报告的终止)
- 检查他们特定设备/系统版本的崩溃报告
- 询问具体的复现步骤
- 如果仍无结果:请求问题的屏幕录制
回复模板:
"我已检查了崩溃报告,目前未找到符合你描述的崩溃。为了帮助调查:(1) 能否确认你使用的是TestFlight版本?(2) 具体是什么情况——应用是突然关闭、卡顿还是显示错误?(3) 发生前你正在做什么?这些信息能帮我找到问题。"
重要性: "我这里正常"会破坏信任。在敷衍之前一定要彻底调查。
Scenario 2: "Crash rate spiked after latest TestFlight build, need to fix ASAP"
场景2:"最新TestFlight版本发布后崩溃率飙升,需要立即修复"
Pressure: Time pressure, tempted to guess at fix based on code changes
Correct approach:
- Open Organizer → Crashes → Filter to the new build
- Group crashes by exception type (look for the dominant signature)
- Identify the #1 crash by frequency
- Symbolicate and read the crash report fully
- Understand the cause before writing any fix
- If possible, reproduce locally
- Fix the verified cause, not a guess
Why this matters: Rushed guesses often introduce new bugs or miss the real issue. 15 minutes of proper triage prevents hours of misdirected debugging.
压力: 时间紧迫,容易想根据代码改动猜测修复方案
正确做法:
- 打开Organizer → 崩溃 → 筛选到新版本
- 按异常类型分组崩溃(寻找占比最高的崩溃类型)
- 确定出现频率最高的头号崩溃
- 符号化并完整阅读崩溃报告
- 先理解原因再编写修复
- 如果可能,在本地复现
- 修复已确认的原因,而非猜测
重要性: 仓促的猜测往往会引入新Bug或错过真正的问题。15分钟的正确处理能避免数小时的无效调试。
Scenario 3: "Crash report is symbolicated but I still don't understand it"
场景3:"崩溃报告已符号化但我还是看不懂"
Pressure: Tempted to ignore it or make random changes hoping it helps
Correct approach:
- Paste full crash report into Claude with context
- Ask for interpretation, not just "fix this"
- Research exception type if unfamiliar
- If still unclear after research, add logging around the crash site:
swift
func suspectFunction() {
logger.debug("Entering suspectFunction, state: \(debugDescription)")
defer { logger.debug("Exiting suspectFunction") }
// ... existing code ...
}- Ship instrumented build to TestFlight
- Wait for reproduction with better context
Why this matters: Understanding beats guessing. Logging beats speculation. It's okay to say "I need more information" rather than shipping a random change.
压力: 容易想忽略或随机修改代码碰运气
正确做法:
- 将完整崩溃报告和上下文粘贴到Claude中
- 请求解读,而非直接要求"修复这个"
- 研究不熟悉的异常类型
- 如果研究后仍不清楚,在崩溃位置附近添加日志:
swift
func suspectFunction() {
logger.debug("进入suspectFunction,状态:\(debugDescription)")
defer { logger.debug("退出suspectFunction") }
// ... 现有代码 ...
}- 发布带日志的版本到TestFlight
- 等待包含更多上下文的复现报告
重要性: 理解比猜测更重要。日志比碰运气更可靠。与其随机修改代码,不如说"我需要更多信息"。
Quick Reference
快速参考
Organizer Keyboard Shortcuts
Organizer快捷键
| Action | Shortcut |
|---|---|
| Open Organizer | ⌘⇧O (from Xcode) |
| Refresh | ⌘R |
| 操作 | 快捷键 |
|---|---|
| 打开Organizer | ⌘⇧O(在Xcode中) |
| 刷新 | ⌘R |
Common Exception Codes
常见异常代码
| Code | Meaning |
|---|---|
| Null pointer / bad memory access |
| Memory protection violation |
| Watchdog timeout (main thread blocked) |
| Deadlock detected |
| Thermal event (device too hot) |
| 代码 | 含义 |
|---|---|
| 空指针/无效内存访问 |
| 内存保护违规 |
| Watchdog超时(主线程阻塞) |
| 检测到死锁 |
| 过热事件(设备温度过高) |
Crash Report Sections
崩溃报告部分
| Section | Contains |
|---|---|
| Header | App info, device, OS, date |
| Exception Information | Crash type and codes |
| Termination Reason | Why system killed the process |
| Triggered by Thread | Which thread crashed |
| Application Specific | Error messages, assertions |
| Thread Backtraces | Stack traces for all threads |
| Binary Images | Loaded frameworks and addresses |
| 部分 | 包含内容 |
|---|---|
| 头部 | 应用信息、设备、系统版本、日期 |
| 异常信息 | 崩溃类型和代码 |
| 终止原因 | 系统终止进程的原因 |
| 崩溃线程 | 崩溃的线程 |
| 应用特定信息 | 错误消息、断言 |
| 线程调用栈 | 所有线程的调用栈 |
| 二进制镜像 | 已加载的框架和地址 |
Resources
参考资源
WWDC: 2018-414, 2020-10076, 2020-10078, 2020-10081, 2021-10203, 2021-10258
Docs: /xcode/diagnosing-issues-using-crash-reports-and-device-logs, /xcode/examining-the-fields-in-a-crash-report, /xcode/adding-identifiable-symbol-names-to-a-crash-report, /xcode/identifying-the-cause-of-common-crashes, /xcode/identifying-high-memory-use-with-jetsam-event-reports
Skills: axiom-memory-debugging, axiom-xcode-debugging, axiom-swift-concurrency
Agents: crash-analyzer (automated crash log parsing and analysis)
WWDC: 2018-414, 2020-10076, 2020-10078, 2020-10081, 2021-10203, 2021-10258
文档: /xcode/diagnosing-issues-using-crash-reports-and-device-logs, /xcode/examining-the-fields-in-a-crash-report, /xcode/adding-identifiable-symbol-names-to-a-crash-report, /xcode/identifying-the-cause-of-common-crashes, /xcode/identifying-high-memory-use-with-jetsam-event-reports
技能: axiom-memory-debugging, axiom-xcode-debugging, axiom-swift-concurrency
Agent: crash-analyzer(自动化崩溃日志解析与分析)