debugging-instruments
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDebugging and Instruments
调试与Instruments工具
Contents
目录
LLDB Debugging
LLDB调试
Essential Commands
核心命令
text
(lldb) po myObject # Print object description (calls debugDescription)
(lldb) p myInt # Print with type info (uses LLDB formatter)
(lldb) v myLocal # Frame variable — fast, no code execution
(lldb) bt # Backtrace current thread
(lldb) bt all # Backtrace all threads
(lldb) frame select 3 # Jump to frame #3 in the backtrace
(lldb) thread list # List all threads and their states
(lldb) thread select 4 # Switch to thread #4Use over when you only need a local variable value — it does not
execute code and cannot trigger side effects.
vpotext
(lldb) po myObject # 打印对象描述(调用debugDescription方法)
(lldb) p myInt # 带类型信息打印(使用LLDB格式化器)
(lldb) v myLocal # 帧变量——速度快,不执行代码
(lldb) bt # 当前线程的回溯信息
(lldb) bt all # 所有线程的回溯信息
(lldb) frame select 3 # 跳转到回溯信息中的第3帧
(lldb) thread list # 列出所有线程及其状态
(lldb) thread select 4 # 切换到第4个线程当你只需要获取局部变量的值时,优先使用而非——它不会执行代码,也不会触发副作用。
vpoBreakpoint Management
断点管理
text
(lldb) br set -f ViewModel.swift -l 42 # Break at file:line
(lldb) br set -n viewDidLoad # Break on function name
(lldb) br set -S setValue:forKey: # Break on ObjC selector
(lldb) br modify 1 -c "count > 10" # Add condition to breakpoint 1
(lldb) br modify 1 --auto-continue true # Log and continue (logpoint)
(lldb) br command add 1 # Attach commands to breakpoint
> po self.title
> continue
> DONE
(lldb) br disable 1 # Disable without deleting
(lldb) br delete 1 # Remove breakpointtext
(lldb) br set -f ViewModel.swift -l 42 # 在指定文件的指定行设置断点
(lldb) br set -n viewDidLoad # 根据函数名设置断点
(lldb) br set -S setValue:forKey: # 根据OC选择器设置断点
(lldb) br modify 1 -c "count > 10" # 为编号1的断点添加条件
(lldb) br modify 1 --auto-continue true # 日志断点:记录信息后继续执行
(lldb) br command add 1 # 为编号1的断点附加执行命令
> po self.title
> continue
> DONE
(lldb) br disable 1 # 禁用断点(不删除)
(lldb) br delete 1 # 删除断点Expression Evaluation
表达式求值
text
(lldb) expr myArray.count # Evaluate Swift expression
(lldb) e -l swift -- import UIKit # Import framework in LLDB
(lldb) e -l swift -- self.view.backgroundColor = .red # Modify state at runtime
(lldb) e -l objc -- (void)[CATransaction flush] # Force UI update after changesAfter modifying a view property in the debugger, call
to see the change immediately without resuming execution.
CATransaction.flush()text
(lldb) expr myArray.count # 求值Swift表达式
(lldb) e -l swift -- import UIKit # 在LLDB中导入框架
(lldb) e -l swift -- self.view.backgroundColor = .red # 在运行时修改状态
(lldb) e -l objc -- (void)[CATransaction flush] # 修改后强制更新UI在调试器中修改视图属性后,调用可以立即看到变化,无需恢复执行。
CATransaction.flush()Watchpoints
观察点
text
(lldb) w set v self.score # Break when score changes
(lldb) w set v self.score -w read # Break when score is read
(lldb) w modify 1 -c "self.score > 100" # Conditional watchpoint
(lldb) w list # Show active watchpoints
(lldb) w delete 1 # Remove watchpointWatchpoints are hardware-backed (limited to ~4 on ARM). Use them to find
unexpected mutations — the debugger stops at the exact line that changes
the value.
text
(lldb) w set v self.score # 当score值变化时触发断点
(lldb) w set v self.score -w read # 当score被读取时触发断点
(lldb) w modify 1 -c "self.score > 100" # 为编号1的观察点添加条件
(lldb) w list # 显示所有活跃的观察点
(lldb) w delete 1 # 删除观察点观察点由硬件支持(ARM架构下最多支持约4个)。使用它们可以定位意外的变量修改——调试器会在修改值的代码行处暂停。
Symbolic Breakpoints
符号断点
Set breakpoints on methods without knowing the file. Useful for framework
or system code:
text
(lldb) br set -n "UIViewController.viewDidLoad"
(lldb) br set -r ".*networkError.*" # Regex on symbol name
(lldb) br set -n malloc_error_break # Catch malloc corruption
(lldb) br set -n UIViewAlertForUnsatisfiableConstraints # Auto Layout issuesIn Xcode, use the Breakpoint Navigator (+) to add symbolic breakpoints for
common diagnostics like or .
-[UIApplication main]swift_willThrow无需知道文件路径即可为方法设置断点,适用于框架或系统代码:
text
(lldb) br set -n "UIViewController.viewDidLoad"
(lldb) br set -r ".*networkError.*" # 根据符号名的正则表达式设置断点
(lldb) br set -n malloc_error_break # 捕获内存分配损坏问题
(lldb) br set -n UIViewAlertForUnsatisfiableConstraints # 自动布局问题在Xcode中,使用断点导航器(+按钮)可以添加常见诊断场景的符号断点,比如或。
-[UIApplication main]swift_willThrowMemory Debugging
内存调试
Memory Graph Debugger Workflow
Memory Graph Debugger使用流程
- Run the app in Debug configuration.
- Reproduce the suspected leak (navigate to a screen, then back).
- Tap the Memory Graph button in Xcode's debug bar.
- Look for purple warning icons — these indicate leaked objects.
- Select a leaked object to see its reference graph and backtrace.
Enable Malloc Stack Logging (Scheme > Diagnostics) before running so
the Memory Graph shows allocation backtraces.
- 以Debug配置运行应用。
- 复现疑似内存泄漏的场景(比如进入某个页面后返回)。
- 点击Xcode调试栏中的内存图按钮。
- 寻找紫色警告图标——这些表示存在泄漏的对象。
- 选择泄漏对象查看其引用图和回溯信息。
运行前启用Malloc Stack Logging(Scheme > Diagnostics),这样内存图会显示分配回溯信息。
Common Retain Cycle Patterns
常见引用循环模式
Closure capturing self strongly:
swift
// LEAK — closure holds strong reference to self
class ProfileViewModel {
var onUpdate: (() -> Void)?
func startObserving() {
onUpdate = {
self.refresh() // strong capture of self
}
}
}
// FIXED — use [weak self]
func startObserving() {
onUpdate = { [weak self] in
self?.refresh()
}
}Strong delegate reference:
swift
// LEAK — strong delegate creates a cycle
protocol DataDelegate: AnyObject {
func didUpdate()
}
class DataManager {
var delegate: DataDelegate? // should be weak
}
// FIXED — weak delegate
class DataManager {
weak var delegate: DataDelegate?
}Timer retaining target:
swift
// LEAK — Timer.scheduledTimer retains its target
timer = Timer.scheduledTimer(
timeInterval: 1.0, target: self,
selector: #selector(tick), userInfo: nil, repeats: true
)
// FIXED — use closure-based API with [weak self]
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.tick()
}闭包强引用self:
swift
// 内存泄漏——闭包强引用self
class ProfileViewModel {
var onUpdate: (() -> Void)?
func startObserving() {
onUpdate = {
self.refresh() // 强引用self
}
}
}
// 修复方案——使用[weak self]
func startObserving() {
onUpdate = { [weak self] in
self?.refresh()
}
}代理强引用:
swift
// 内存泄漏——强引用代理形成循环
protocol DataDelegate: AnyObject {
func didUpdate()
}
class DataManager {
var delegate: DataDelegate? // 应该使用weak
}
// 修复方案——使用weak代理
class DataManager {
weak var delegate: DataDelegate?
}Timer强引用目标对象:
swift
// 内存泄漏——Timer.scheduledTimer会强引用目标对象
timer = Timer.scheduledTimer(
timeInterval: 1.0, target: self,
selector: #selector(tick), userInfo: nil, repeats: true
)
// 修复方案——使用基于闭包的API并配合[weak self]
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.tick()
}Instruments: Allocations and Leaks
Instruments:内存分配与泄漏检测
- Allocations template: Track memory growth over time. Use the "Mark Generation" feature to isolate allocations created between user actions (e.g., open/close a screen).
- Leaks template: Automatically detects reference cycles at runtime. Run alongside Allocations for a complete picture.
- Filter by your app's module name to exclude system allocations.
- Allocations模板:跟踪内存随时间的增长情况。使用“Mark Generation”功能可以隔离用户操作之间(比如打开/关闭页面)创建的内存分配。
- Leaks模板:在运行时自动检测引用循环。配合Allocations模板使用可以获得完整的内存情况。
- 按应用的模块名称过滤,排除系统内存分配。
Malloc Stack Logging
Malloc Stack Logging
Enable in Scheme > Run > Diagnostics > Malloc Stack Logging (All
Allocations). This records the call stack for every allocation, letting
the Memory Graph Debugger and CLI show where objects were created.
leaksbash
undefined在Scheme > Run > Diagnostics > Malloc Stack Logging (All Allocations)中启用。它会记录每次内存分配的调用栈,让Memory Graph Debugger和命令行工具可以显示对象的创建位置。
leaksbash
undefinedCLI leak detection
命令行泄漏检测
leaks --atExit -- ./MyApp.app/MyApp
leaks --atExit -- ./MyApp.app/MyApp
Symbolicate with dSYMs for readable stacks
使用dSYM文件符号化以获得可读的调用栈
undefinedundefinedHang Diagnostics
卡顿诊断
Identifying Main Thread Hangs
识别主线程卡顿
A hang occurs when the main thread is blocked for > 250ms (noticeable) or
1s (severe). Common detection tools:
- Thread Checker (Xcode Diagnostics): warns about non-main-thread UI calls
- os_signpost and : mark intervals for Instruments
OSSignposter - MetricKit hang diagnostics: production hang detection (see
skill for
metrickit-diagnostics)MXHangDiagnostic
swift
import os
let signposter = OSSignposter(subsystem: "com.example.app", category: "DataLoad")
func loadData() async {
let state = signposter.beginInterval("loadData")
let result = await fetchFromNetwork()
signposter.endInterval("loadData", state)
process(result)
}当主线程被阻塞超过250ms(可感知卡顿)或1s(严重卡顿)时,就会出现卡顿。常用检测工具:
- Thread Checker(Xcode Diagnostics):警告非主线程的UI操作
- os_signpost和:为Instruments标记时间区间
OSSignposter - MetricKit卡顿诊断:生产环境卡顿检测(查看技能中的
metrickit-diagnostics)MXHangDiagnostic
swift
import os
let signposter = OSSignposter(subsystem: "com.example.app", category: "DataLoad")
func loadData() async {
let state = signposter.beginInterval("loadData")
let result = await fetchFromNetwork()
signposter.endInterval("loadData", state)
process(result)
}Using the Time Profiler
使用Time Profiler
- Product > Profile (Cmd+I) to launch Instruments.
- Select the Time Profiler template.
- Record while reproducing the slow interaction.
- Focus on the main thread — sort by "Weight" to find hot paths.
- Check "Hide System Libraries" to see only your code.
- Double-click a heavy frame to jump to source.
- 选择Product > Profile(Cmd+I)启动Instruments。
- 选择Time Profiler模板。
- 复现缓慢交互的同时进行录制。
- 聚焦主线程——按“Weight”排序找到热点路径。
- 勾选“Hide System Libraries”只查看自己的代码。
- 双击权重高的帧跳转到源代码。
Common Hang Causes
常见卡顿原因
| Cause | Symptom | Fix |
|---|---|---|
| Synchronous I/O on main thread | Network/file reads block UI | Move to |
| Lock contention | Main thread waiting on a lock held by background work | Use actors or reduce lock scope |
| Layout thrashing | Repeated | Batch layout changes, avoid forced layout |
| JSON parsing large payloads | UI freezes during data load | Parse on a background thread |
| Synchronous image decoding | Scroll jank on image-heavy lists | Use |
| 原因 | 症状 | 修复方案 |
|---|---|---|
| 主线程同步I/O | 网络/文件读取阻塞UI | 移到 |
| 锁竞争 | 主线程等待后台线程持有的锁 | 使用actor或缩小锁的作用域 |
| 布局抖动 | 重复调用 | 批量处理布局变化,避免强制布局 |
| 大JSON解析 | 数据加载时UI冻结 | 在后台线程解析 |
| 同步图片解码 | 图片密集列表滚动卡顿 | 使用 |
Build Failure Triage
构建故障排查
Reading Compiler Diagnostics
解读编译器诊断信息
- Start from the first error — subsequent errors are often cascading.
- Search for the error code (e.g., ) in the build log.
error: cannot convert - Use Report Navigator (Cmd+9) for the full build log with timestamps.
- 从第一个错误开始排查——后续错误通常是连锁反应。
- 在构建日志中搜索错误代码(比如)。
error: cannot convert - 使用报告导航器(Cmd+9)查看带时间戳的完整构建日志。
SPM Dependency Resolution
SPM依赖解析
text
undefinedtext
undefinedCommon: version conflict
常见问题:版本冲突
error: Dependencies could not be resolved because root depends on 'Package' 1.0.0..<2.0.0
error: Dependencies could not be resolved because root depends on 'Package' 1.0.0..<2.0.0
Fix: check Package.resolved and update version ranges
修复方案:检查Package.resolved并更新版本范围
Reset package caches if needed:
必要时重置包缓存:
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf .build
swift package resolve
undefinedrm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf .build
swift package resolve
undefinedModule Not Found / Linker Errors
模块未找到/链接器错误
| Error | Check |
|---|---|
| Target membership, import paths, framework search paths |
| Linking phase missing framework, wrong architecture |
| Two targets define same symbol; check for ObjC naming collisions |
Build settings to inspect first:
FRAMEWORK_SEARCH_PATHSOTHER_LDFLAGSSWIFT_INCLUDE_PATHS- (for XCFrameworks)
BUILD_LIBRARY_FOR_DISTRIBUTION
| 错误 | 检查项 |
|---|---|
| 目标成员资格、导入路径、框架搜索路径 |
| 链接阶段缺少框架、架构不匹配 |
| 两个目标定义了相同的符号;检查OC命名冲突 |
首先检查以下构建设置:
FRAMEWORK_SEARCH_PATHSOTHER_LDFLAGSSWIFT_INCLUDE_PATHS- (适用于XCFrameworks)
BUILD_LIBRARY_FOR_DISTRIBUTION
Instruments Overview
Instruments工具概述
Template Selection Guide
模板选择指南
| Template | Use When |
|---|---|
| Time Profiler | CPU is high, UI feels slow, need to find hot code paths |
| Allocations | Memory grows over time, need to track object lifetimes |
| Leaks | Suspect retain cycles or abandoned objects |
| Network | Inspecting HTTP request/response timing and payloads |
| SwiftUI | Profiling view body evaluations and update frequency |
| Core Animation | Frame drops, off-screen rendering, blending issues |
| Energy Log | Battery drain, background energy impact |
| File Activity | Excessive disk I/O, slow file operations |
| System Trace | Thread scheduling, syscalls, virtual memory faults |
| 模板 | 适用场景 |
|---|---|
| Time Profiler | CPU占用高、UI响应慢,需要找到热点代码路径 |
| Allocations | 内存持续增长,需要跟踪对象生命周期 |
| Leaks | 疑似引用循环或废弃对象 |
| Network | 检查HTTP请求/响应的时间和负载 |
| SwiftUI | 分析视图body的求值频率和更新情况 |
| Core Animation | 掉帧、离屏渲染、混合问题 |
| Energy Log | 电池耗电快、后台能耗高 |
| File Activity | 磁盘I/O过多、文件操作缓慢 |
| System Trace | 线程调度、系统调用、虚拟内存错误 |
xctrace CLI for CI Profiling
用于CI环境分析的xctrace命令行工具
bash
undefinedbash
undefinedRecord a trace from the command line
从命令行录制跟踪数据
xcrun xctrace record --device "My iPhone"
--template "Time Profiler"
--output profile.trace
--launch MyApp.app
--template "Time Profiler"
--output profile.trace
--launch MyApp.app
xcrun xctrace record --device "My iPhone"
--template "Time Profiler"
--output profile.trace
--launch MyApp.app
--template "Time Profiler"
--output profile.trace
--launch MyApp.app
Export trace data as XML for automated analysis
将跟踪数据导出为XML以进行自动化分析
xcrun xctrace export --input profile.trace --xpath '/trace-toc/run/data/table'
xcrun xctrace export --input profile.trace --xpath '/trace-toc/run/data/table'
List available templates
列出可用模板
xcrun xctrace list templates
xcrun xctrace list templates
List connected devices
列出已连接设备
xcrun xctrace list devices
Use `xctrace` in CI pipelines to catch performance regressions
automatically. Compare exported metrics between builds.xcrun xctrace list devices
在CI流水线中使用`xctrace`可以自动捕获性能回归问题。对比不同构建版本的导出指标。Common Mistakes
常见误区
DON'T: Use print() for debugging instead of os.Logger
不要:使用print()调试而非os.Logger
print()swift
// WRONG — unstructured, not filterable, stays in release builds
print("user tapped button, state: \(viewModel.state)")
print("network response: \(data)")
// CORRECT — structured logging with Logger
import os
let logger = Logger(subsystem: "com.example.app", category: "UI")
logger.debug("Button tapped, state: \(viewModel.state, privacy: .public)")
logger.info("Network response received, bytes: \(data.count)")Logger.debugprint()swift
// 错误用法——非结构化、不可过滤,会保留在Release构建中
print("user tapped button, state: \(viewModel.state)")
print("network response: \(data)")
// 正确用法——使用Logger进行结构化日志
import os
let logger = Logger(subsystem: "com.example.app", category: "UI")
logger.debug("Button tapped, state: \(viewModel.state, privacy: .public)")
logger.info("Network response received, bytes: \(data.count)")Logger.debugDON'T: Forget to enable Malloc Stack Logging before memory debugging
不要:内存调试前忘记启用Malloc Stack Logging
Without Malloc Stack Logging, the Memory Graph Debugger shows leaked
objects but cannot display allocation backtraces, making it difficult to
find the code that created them.
swift
// WRONG — open Memory Graph without enabling Malloc Stack Logging
// Result: leaked objects visible but no allocation backtrace
// CORRECT — enable BEFORE running:
// Scheme > Run > Diagnostics > check "Malloc Stack Logging: All Allocations"
// Then run, reproduce the leak, and open Memory Graph如果没有启用Malloc Stack Logging,Memory Graph Debugger会显示泄漏的对象,但无法显示分配调用栈,难以找到创建对象的代码。
swift
// 错误用法——未启用Malloc Stack Logging就打开Memory Graph
// 结果:可以看到泄漏对象,但没有分配调用栈
// 正确用法——运行前启用:
// Scheme > Run > Diagnostics > 勾选"Malloc Stack Logging: All Allocations"
// 然后运行应用,复现泄漏场景,再打开Memory GraphDON'T: Debug optimized code expecting full variable visibility
不要:调试优化后的代码并期望完整的变量可见性
In Release (optimized) builds, the compiler may inline functions, eliminate
variables, and reorder code. LLDB cannot display optimized-away values.
swift
// WRONG — profiling with Debug build, debugging with Release build
// Debug builds: extra runtime checks distort perf measurements
// Release builds: variables show as "<optimized out>" in debugger
// CORRECT approach:
// Debugging: use Debug configuration (full symbols, no optimization)
// Profiling: use Release configuration (realistic performance)在Release(优化)构建中,编译器可能会内联函数、消除变量、重排代码。LLDB无法显示被优化掉的变量值。
swift
// 错误用法——用Debug构建做性能分析,用Release构建做调试
// Debug构建:额外的运行时检查会影响性能测量结果
// Release构建:变量在调试器中显示为"<optimized out>"
// 正确做法:
// 调试:使用Debug配置(完整符号,无优化)
// 性能分析:使用Release配置(真实性能)DON'T: Stop on every loop iteration without conditional breakpoints
不要:不使用条件断点就暂停循环的每次迭代
Breaking on every iteration wastes time and makes it hard to find the
specific case you care about.
swift
// WRONG — breakpoint on line inside loop, stops 10,000 times
for item in items {
process(item) // breakpoint here stops on EVERY item
}
// CORRECT — use a conditional breakpoint:
// (lldb) br set -f MyFile.swift -l 42 -c "item.id == targetID"
// Or in Xcode: right-click breakpoint > Edit > add Condition在循环内的代码行设置断点会导致每次迭代都暂停,浪费时间且难以找到关心的特定场景。
swift
// 错误用法——在循环内的代码行设置断点,会暂停10000次
for item in items {
process(item) // 此处的断点会在每个item时暂停
}
// 正确用法——使用条件断点:
// (lldb) br set -f MyFile.swift -l 42 -c "item.id == targetID"
// 或在Xcode中:右键断点 > 编辑 > 添加条件DON'T: Ignore Thread Sanitizer warnings
不要:忽略Thread Sanitizer警告
Thread Sanitizer (TSan) warnings indicate data races that may only crash
intermittently. They are real bugs, not false positives.
swift
// WRONG — ignoring TSan warning about concurrent access
var cache: [String: Data] = [:] // accessed from multiple threads
// CORRECT — protect shared mutable state
actor CacheActor {
var cache: [String: Data] = [:]
func get(_ key: String) -> Data? { cache[key] }
func set(_ key: String, _ value: Data) { cache[key] = value }
}Enable TSan: Scheme > Run > Diagnostics > Thread Sanitizer. Note: TSan
cannot run simultaneously with Address Sanitizer.
Thread Sanitizer(TSan)警告表示存在数据竞争,可能会导致间歇性崩溃。它们是真实的bug,不是误报。
swift
// 错误用法——忽略TSan关于并发访问的警告
var cache: [String: Data] = [:] // 被多个线程访问
// 正确用法——保护共享可变状态
actor CacheActor {
var cache: [String: Data] = [:]
func get(_ key: String) -> Data? { cache[key] }
func set(_ key: String, _ value: Data) { cache[key] = value }
}启用TSan:Scheme > Run > Diagnostics > Thread Sanitizer。注意:TSan无法与Address Sanitizer同时运行。
Review Checklist
检查清单
- Using instead of
os.Loggerfor diagnostic outputprint() - Malloc Stack Logging enabled before memory debugging sessions
- Memory Graph Debugger checked after dismiss/dealloc flows
- Delegates declared as to prevent retain cycles
weak var - Closures stored as properties use capture lists
[weak self] - Timers use closure-based API with
[weak self] - Thread Sanitizer enabled in test schemes
- No synchronous I/O or heavy computation on the main thread
- Time Profiler run on Release build for performance baselines
- Build failures triaged from the first error in the build log
- used for custom performance intervals
OSSignposter - Conditional breakpoints used for loop/collection debugging
- 使用而非
os.Logger输出诊断信息print() - 内存调试会话前已启用Malloc Stack Logging
- 关闭/销毁流程后已检查Memory Graph Debugger
- 代理声明为以避免引用循环
weak var - 作为属性存储的闭包使用捕获列表
[weak self] - Timer使用基于闭包的API并配合
[weak self] - 测试Scheme中已启用Thread Sanitizer
- 主线程无同步I/O或重计算操作
- 已使用Release配置运行Time Profiler获取性能基准
- 构建故障从第一个错误开始排查
- 使用标记自定义性能区间
OSSignposter - 循环/集合调试使用条件断点
References
参考资料
- Logging (unified logging system)
- Logger
- OSSignposter
- Generating log messages from your code
- Recording performance data (signposts)
- Diagnosing memory, thread, and crash issues early
- Data races
- Reducing your app's memory use
- Profiling apps using Instruments
- Analyzing the performance of your shipping app
- LLDB command reference:
references/lldb-patterns.md - Instruments template guide:
references/instruments-guide.md
- 日志(统一日志系统)
- Logger
- OSSignposter
- 从代码生成日志消息
- 记录性能数据(Signposts)
- 尽早诊断内存、线程和崩溃问题
- 数据竞争
- 减少应用内存使用
- 使用Instruments分析应用
- 分析已发布应用的性能
- LLDB命令参考:
references/lldb-patterns.md - Instruments模板指南:
references/instruments-guide.md