debugging-instruments by dpearson2699/swift-ios-skills
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill debugging-instruments(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 号线程
当你只需要局部变量值时,使用 v 而非 po — 它不执行代码,因此不会触发副作用。
(lldb) br set -f ViewModel.swift -l 42 # 在文件:行号处中断
(lldb) br set -n viewDidLoad # 在函数名处中断
(lldb) br set -S setValue:forKey: # 在 ObjC 选择器处中断
(lldb) br modify 1 -c "count > 10" # 为断点 1 添加条件
(lldb) br modify 1 --auto-continue true # 记录并继续(日志点)
(lldb) br command add 1 # 为断点附加命令
> po self.title
> continue
> DONE
(lldb) br disable 1 # 禁用但不删除断点
(lldb) br delete 1 # 移除断点
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
(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() 可以立即看到更改,而无需恢复执行。
(lldb) w set v self.score # 当 score 改变时中断
(lldb) w set v self.score -w read # 当 score 被读取时中断
(lldb) w modify 1 -c "self.score > 100" # 条件观察点
(lldb) w list # 显示活动的观察点
(lldb) w delete 1 # 移除观察点
观察点由硬件支持(在 ARM 上限制约为 4 个)。使用它们来查找意外的修改 — 调试器会在更改该值的精确行处停止。
在不了解文件的情况下在方法上设置断点。适用于框架或系统代码:
(lldb) br set -n "UIViewController.viewDidLoad"
(lldb) br set -r ".*networkError.*" # 符号名正则表达式
(lldb) br set -n malloc_error_break # 捕获 malloc 损坏
(lldb) br set -n UIViewAlertForUnsatisfiableConstraints # 自动布局问题
在 Xcode 中,使用断点导航器 (+) 为常见诊断添加符号断点,例如 -[UIApplication main] 或 swift_willThrow。
在运行前启用 Malloc Stack Logging(Scheme > Diagnostics),以便内存图显示分配回溯。
闭包强捕获 self:
// 泄漏 — 闭包持有对 self 的强引用
class ProfileViewModel {
var onUpdate: (() -> Void)?
func startObserving() {
onUpdate = {
self.refresh() // 强捕获 self
}
}
}
// 已修复 — 使用 [weak self]
func startObserving() {
onUpdate = { [weak self] in
self?.refresh()
}
}
强委托引用:
// 泄漏 — 强委托创建了循环
protocol DataDelegate: AnyObject {
func didUpdate()
}
class DataManager {
var delegate: DataDelegate? // 应为 weak
}
// 已修复 — weak 委托
class DataManager {
weak var delegate: DataDelegate?
}
Timer 保留目标:
// 泄漏 — 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()
}
在 Scheme > Run > Diagnostics > Malloc Stack Logging (All Allocations) 中启用。这会记录每次分配的调用栈,让内存图调试器和 leaks 命令行工具显示对象创建的位置。
# 命令行泄漏检测
leaks --atExit -- ./MyApp.app/MyApp
# 使用 dSYMs 进行符号化以获得可读的调用栈
当主线程被阻塞超过 250 毫秒(可察觉)或
1 秒(严重)时,就会发生卡顿。常见的检测工具:
Thread Checker(Xcode 诊断):警告非主线程的 UI 调用
os_signpost 和 OSSignposter:为 Instruments 标记时间间隔
MetricKit 卡顿诊断:生产环境卡顿检测(有关 MXHangDiagnostic,请参阅 metrickit-diagnostics 技能)
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) }
| 原因 | 症状 | 修复方案 |
|---|---|---|
| 主线程上的同步 I/O | 网络/文件读取阻塞 UI | 移至 Task { } 或后台 actor |
| 锁争用 | 主线程等待后台工作持有的锁 | 使用 actors 或减少锁作用域 |
| 布局抖动 | 重复调用 layoutSubviews | 批量布局更改,避免强制布局 |
| JSON 解析大负载 | 数据加载期间 UI 冻结 | 在后台线程解析 |
| 同步图像解码 | 图像密集型列表滚动卡顿 | 使用 AsyncImage 或在主线程外解码 |
error: cannot convert)。# 常见:版本冲突
error: Dependencies could not be resolved because root depends on 'Package' 1.0.0..<2.0.0
# 修复:检查 Package.resolved 并更新版本范围
# 如果需要,重置包缓存:
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf .build
swift package resolve
| 错误 | 检查项 |
|---|---|
No such module 'Foo' | 目标成员资格、导入路径、框架搜索路径 |
Undefined symbol | 链接阶段缺少框架、架构错误 |
duplicate symbol | 两个目标定义了相同的符号;检查 ObjC 命名冲突 |
首先检查的构建设置:
FRAMEWORK_SEARCH_PATHSOTHER_LDFLAGSSWIFT_INCLUDE_PATHSBUILD_LIBRARY_FOR_DISTRIBUTION(针对 XCFrameworks)| 模板 | 使用场景 |
|---|---|
| Time Profiler | CPU 占用高,UI 感觉慢,需要找到热点代码路径 |
| Allocations | 内存随时间增长,需要跟踪对象生命周期 |
| Leaks | 怀疑存在循环引用或废弃对象 |
| Network | 检查 HTTP 请求/响应时间和负载 |
| SwiftUI | 分析视图 body 求值和更新频率 |
| Core Animation | 掉帧、离屏渲染、混合问题 |
| Energy Log | 电池消耗、后台能耗影响 |
| File Activity | 磁盘 I/O 过多、文件操作缓慢 |
| System Trace | 线程调度、系统调用、虚拟内存故障 |
# 从命令行录制跟踪记录
xcrun xctrace record --device "My iPhone" \
--template "Time Profiler" \
--output profile.trace \
--launch MyApp.app
# 将跟踪数据导出为 XML 以进行自动化分析
xcrun xctrace export --input profile.trace --xpath '/trace-toc/run/data/table'
# 列出可用模板
xcrun xctrace list templates
# 列出已连接的设备
xcrun xctrace list devices
在 CI 流水线中使用 xctrace 自动捕获性能回归。比较不同构建之间导出的指标。
print() 输出不可过滤,没有日志级别,并且不会自动从发布版本中剥离。它会污染控制台,并且无法隔离相关输出。
// 错误 — 非结构化,不可过滤,会保留在发布版本中
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 消息会出现在 Console.app 中,可按子系统和类别进行过滤,并且 .debug 消息会自动从发布版本中排除。
没有 Malloc Stack Logging,内存图调试器会显示泄漏的对象但无法显示分配回溯,这使得难以找到创建它们的代码。
// 错误 — 未启用 Malloc Stack Logging 就打开内存图
// 结果:泄漏对象可见但没有分配回溯
// 正确 — 在运行前启用:
// Scheme > Run > Diagnostics > 勾选 "Malloc Stack Logging: All Allocations"
// 然后运行,复现泄漏,再打开内存图
在 Release(优化)构建中,编译器可能会内联函数、消除变量并重新排序代码。LLDB 无法显示被优化掉的值。
// 错误 — 使用 Debug 构建进行分析,使用 Release 构建进行调试
// Debug 构建:额外的运行时检查会扭曲性能测量
// Release 构建:变量在调试器中显示为 "<optimized out>"
// 正确方法:
// 调试:使用 Debug 配置(完整符号,无优化)
// 性能分析:使用 Release 配置(真实的性能)
在每次迭代时中断会浪费时间,并且难以找到你关心的特定情况。
// 错误 — 在循环内的行上设置断点,会中断 10,000 次
for item in items {
process(item) // 这里的断点会在每个 item 上停止
}
// 正确 — 使用条件断点:
// (lldb) br set -f MyFile.swift -l 42 -c "item.id == targetID"
// 或在 Xcode 中:右键点击断点 > Edit > 添加 Condition
Thread Sanitizer (TSan) 警告指示可能存在数据竞争,这些竞争可能只会间歇性地导致崩溃。它们是真正的错误,而非误报。
// 错误 — 忽略关于并发访问的 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 同时运行。
os.Logger 而非 print() 进行诊断输出weak var 以防止循环引用[weak self] 捕获列表[weak self]OSSignposter 进行自定义性能区间标记references/lldb-patterns.mdreferences/instruments-guide.md每周安装次数
343
代码仓库
GitHub Stars
269
首次出现
Mar 8, 2026
安全审计
安装于
codex340
amp337
cline337
github-copilot337
kimi-cli337
gemini-cli337
(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 #4
Use v over po when you only need a local variable value — it does not execute code and cannot trigger side effects.
(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 breakpoint
(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 changes
After modifying a view property in the debugger, call CATransaction.flush() to see the change immediately without resuming execution.
(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 watchpoint
Watchpoints 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.
Set breakpoints on methods without knowing the file. Useful for framework or system code:
(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 issues
In Xcode, use the Breakpoint Navigator (+) to add symbolic breakpoints for common diagnostics like -[UIApplication main] or swift_willThrow.
Enable Malloc Stack Logging (Scheme > Diagnostics) before running so the Memory Graph shows allocation backtraces.
Closure capturing self strongly:
// 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:
// 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:
// 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()
}
Enable in Scheme > Run > Diagnostics > Malloc Stack Logging (All Allocations). This records the call stack for every allocation, letting the Memory Graph Debugger and leaks CLI show where objects were created.
# CLI leak detection
leaks --atExit -- ./MyApp.app/MyApp
# Symbolicate with dSYMs for readable stacks
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 OSSignposter: mark intervals for Instruments
MetricKit hang diagnostics: production hang detection (see metrickit-diagnostics skill for MXHangDiagnostic)
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) }
| Cause | Symptom | Fix |
|---|---|---|
| Synchronous I/O on main thread | Network/file reads block UI | Move to Task { } or background actor |
| Lock contention | Main thread waiting on a lock held by background work | Use actors or reduce lock scope |
| Layout thrashing | Repeated layoutSubviews calls | 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 AsyncImage or decode off main thread |
error: cannot convert) in the build log.# Common: version conflict
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
# Reset package caches if needed:
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf .build
swift package resolve
| Error | Check |
|---|---|
No such module 'Foo' | Target membership, import paths, framework search paths |
Undefined symbol | Linking phase missing framework, wrong architecture |
duplicate symbol | Two targets define same symbol; check for ObjC naming collisions |
Build settings to inspect first:
FRAMEWORK_SEARCH_PATHSOTHER_LDFLAGSSWIFT_INCLUDE_PATHSBUILD_LIBRARY_FOR_DISTRIBUTION (for XCFrameworks)| 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 |
# Record a trace from the command line
xcrun xctrace record --device "My iPhone" \
--template "Time Profiler" \
--output profile.trace \
--launch MyApp.app
# Export trace data as XML for automated analysis
xcrun xctrace export --input profile.trace --xpath '/trace-toc/run/data/table'
# List available 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.
print() output is not filterable, has no log levels, and is not automatically stripped from release builds. It pollutes the console and makes it impossible to isolate relevant output.
// 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 messages appear in Console.app with filtering by subsystem and category, and .debug messages are automatically excluded from release builds.
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.
// 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
In Release (optimized) builds, the compiler may inline functions, eliminate variables, and reorder code. LLDB cannot display optimized-away values.
// 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)
Breaking on every iteration wastes time and makes it hard to find the specific case you care about.
// 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
Thread Sanitizer (TSan) warnings indicate data races that may only crash intermittently. They are real bugs, not false positives.
// 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.
os.Logger instead of print() for diagnostic outputweak var to prevent retain cycles[weak self] capture lists[weak self]OSSignposter used for custom performance intervalsreferences/lldb-patterns.mdreferences/instruments-guide.mdWeekly Installs
343
Repository
GitHub Stars
269
First Seen
Mar 8, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex340
amp337
cline337
github-copilot337
kimi-cli337
gemini-cli337
Vue.js开发指南:最佳实践、组件设计与响应式编程核心原则
1,500 周安装