mcpField
D
debugging-instruments

debugging-instruments

github.com/dpearson2699开发1.2k4.1k 次安装13 周前更新
代码仓库

关于 debugging-instruments

面向 Apple 平台开发者的调试与 Instruments 技能指南,涵盖 LLDB、内存、卡顿和构建失败排查。还介绍性能分析工具选择及使用 xctrace 进行命令行采样。

主要功能

  1. 01LLDB 断点与状态检查
  2. 02内存图和泄漏排查
  3. 03主线程卡顿诊断
  4. 04编译与依赖故障排查
  5. 05Instruments 模板选择
  6. 06xctrace 命令行采样

适用场景

  1. 01定位崩溃或异常状态
  2. 02调查泄漏和内存持续增长
  3. 03分析交互卡顿和性能热点
  4. 04排查构建失败及性能回归

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill debugging-instruments

安装后在 Claude Code 中输入/debugging-instruments确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
instruments-guide.md
lldb-patterns.md

详细介绍

调试与 Instruments

在这里记录交互式图表和 Instruments 分析。详细的 .memgraph 命令行所有权/增长分析和 ETTrace 工作请转到对应的专用技能。

目录

LLDB 调试

从一套简洁、可重复的工作流程开始:

  1. 在 Debug 构建中重现问题,并在范围最小且有用的位置设置断点。
  2. 不执行代码,检查局部变量,然后捕获当前调用栈。
  3. 转到相关帧或线程,确认故障状态。
  4. 只有在问题状态的转换仍不明确时,才添加条件或监视点。
(lldb) br set -f ViewModel.swift -l 42     # Stop at file and line
(lldb) v myLocal                           # Inspect without executing code
(lldb) po myObject                         # Use debugDescription when needed
(lldb) bt all                              # Capture every thread's backtrace
(lldb) frame select 3                      # Inspect a relevant frame
(lldb) br modify 1 -c "count > 10"         # Narrow a noisy breakpoint
(lldb) w set v self.score                  # Stop on an unexpected write

只需查看局部变量的值时,使用 v 而不是 po——它不会执行代码,也不会触发副作用。表达式求值可能会执行代码或修改程序状态,而硬件监视点数量有限,因此应谨慎使用这两者。

加载 references/lldb-patterns.md,查看完整的检查、断点/日志点、表达式、监视点、线程导航和符号断点命令表。

内存调试

Memory Graph Debugger 工作流程
  1. 在 Debug 配置中运行应用。
  2. 重现疑似泄漏的问题(导航到某个屏幕,然后返回)。
  3. 点击 Xcode 调试栏中的 Memory Graph 按钮。
  4. 查找紫色警告图标——它们表示存在泄漏对象。
  5. 选择泄漏对象,查看其引用图和回溯。

运行前启用 Malloc Stack Logging(Scheme > Diagnostics),以便 Memory Graph 显示分配回溯。

常见的循环引用模式

闭包强引用 self:

// 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()
    }
}

代理强引用:

// 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?
}

计时器持有目标对象:

// 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()
}
Instruments:Allocations 和 Leaks
  • Allocations 模板:跟踪内存随时间的增长。使用 “Mark Generation”功能,隔离用户操作期间(例如打开/关闭屏幕)创建的分配。
  • Leaks 模板:检测泄漏的分配,包括进程已无法访问的独立循环引用。与 Allocations 同时运行,以获得完整视图。
  • 按应用的模块名称筛选,以排除系统分配。

对于泄漏或内存增长分析,可组合使用这些工具:在重现步骤前后使用 Allocations 的 Mark Generation,确认保留内存的增长;然后使用 Memory Graph Debugger 检查对象所有权,并通过 Malloc Stack Logging 获取分配调用栈。

Malloc Stack Logging

在 Scheme > Run > Diagnostics > Malloc Stack Logging 中启用。此功能会记录分配回溯,以便 Memory Graph Debugger、Allocations 工具和导出的 .memgraph 文件显示对象的创建位置。

# Inspect an exported memory graph from Xcode or Instruments
leaks MyApp.memgraph

卡顿诊断

识别主线程卡顿

对于离散交互,低于 100 ms 的延迟通常难以察觉。Apple 开发者工具通常会报告超过 250 ms 的主运行循环繁忙时段,但该报告阈值并非产品目标:几百毫秒的延迟仍可能让人感觉应用无响应。常用检测工具:

  • Thread Checker(Xcode Diagnostics):对非主线程调用 UI 发出警告
  • Thread Performance Checker:调试时报告优先级反转
  • 设备端卡顿检测:Developer Settings 会报告设备使用期间发生的卡顿
  • Time Profiler / CPU Profiler / Hitches:分析可重现的卡顿
  • os_signpost 和 OSSignposter:标记时间段,供 Instruments 使用
  • MetricKit 卡顿诊断:检测生产环境中的卡顿(有关 HangDiagnostic 和 iOS 26 兼容性,请参阅 metrickit 技能)
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)
}
使用 Time Profiler
  1. 选择 Product > Profile(Cmd+I)以启动 Instruments。
  2. 选择 Time Profiler 模板。
  3. 重现卡顿操作,同时录制。
  4. 重点查看主线程——按“Weight”排序,找到热点路径。
  5. 勾选“Hide System Libraries”,仅查看自己的代码。
  6. 双击耗时较高的栈帧,跳转到源代码。
常见卡顿原因
原因症状修复方法
在主线程上执行同步 I/O网络/文件读取会阻塞 UI移至 Task { } 或后台 actor
锁竞争主线程等待后台任务持有的锁使用 actors 或缩小锁的作用范围
布局抖动反复调用 layoutSubviews批量处理布局更改,避免强制布局
解析大型 JSON 负载加载数据时 UI 卡顿在后台线程解析
同步解码图像图像较多的列表滚动卡顿使用 AsyncImage 或在主线程之外解码

构建失败排查

阅读编译器诊断信息
  • 从第一个错误开始查看——后续错误往往是连锁错误。
  • 在构建日志中搜索错误代码(例如,error: cannot convert)。
  • 使用 Report Navigator(Cmd+9)查看包含时间戳的完整构建日志。
SPM 依赖解析
# 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
找不到模块/链接器错误
错误检查项
No such module 'Foo'目标成员资格、导入路径、框架搜索路径
Undefined symbol链接阶段缺少框架、架构错误
duplicate symbol两个目标定义了相同符号;检查 ObjC 命名冲突

优先检查以下构建设置:

  • FRAMEWORK_SEARCH_PATHS
  • OTHER_LDFLAGS
  • SWIFT_INCLUDE_PATHS
  • BUILD_LIBRARY_FOR_DISTRIBUTION(用于 XCFrameworks)

Instruments 概览

模板选择指南
模板适用情况
Time ProfilerCPU 占用高、UI 感觉迟缓,需要查找热点代码路径
Allocations内存随时间增长,需要跟踪对象生命周期
Leaks怀疑存在保留环或未释放的对象
Network检查 HTTP 请求/响应的耗时和负载
SwiftUI分析视图 body 的求值和更新频率
Animation Hitches / Core Animation instruments帧丢失、卡顿、混合以及提交/渲染工作
Power Profiler电池耗电、热压力、后台能耗影响
File Activity磁盘 I/O 过多、文件操作缓慢
System Trace线程调度、系统调用、虚拟内存故障
用于 CI 性能分析的 xctrace CLI
# Record a trace from the command line
xcrun xctrace record --device "My iPhone" \
    --template "Time Profiler" \
    --instrument "Allocations" \
    --output profile.trace \
    --launch -- /path/to/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

每次录制使用一个 --template;使用 --instrument 添加其他检测工具。在 CI 流水线中使用 xctrace,自动发现性能回归。比较不同构建导出的指标。

常见错误

不要:使用 print() 调试,而不使用 os.Logger

使用 Logger 设置级别、隐私元数据以及子系统/类别筛选; .debug 会保留在内存中,且不会持久化到发布版本。

// WRONG — unstructured and not filterable by subsystem/category
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)")
不要:在内存调试前忘记启用 Malloc Stack Logging
// 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
不要:调试优化后的代码时期待看到所有变量
// 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)
不要:在没有条件断点的情况下,每次循环迭代都暂停
// 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 警告

Thread Sanitizer(TSan)警告表示存在数据竞争,可能只会间歇性导致崩溃。除非已确认是工具问题,否则应将其视为真实错误。

// 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 }
}

启用 TSan:Scheme > Run > Diagnostics > Thread Sanitizer。对于 iOS、iPadOS、 tvOS、visionOS 和 watchOS 应用,请在 Simulator 中运行 TSan;Apple 文档仅说明 64 位 macOS 应用支持在设备上运行。

审查清单

  • 使用 os.Logger 而非 print() 输出诊断信息
  • 在内存调试会话开始前启用 Malloc Stack Logging
  • 检查关闭或释放流程后的内存图调试器
  • 将代理声明为 weak var,以防止循环引用
  • 属性中存储的闭包使用 [weak self] 捕获列表
  • 计时器使用基于闭包的 API,并使用 [weak self]
  • 在模拟器测试方案中启用线程净化器,以排查竞争问题
  • 不在主线程上执行同步 I/O 或繁重计算
  • 在 Release 构建上运行 Time Profiler,获取性能基线
  • 从构建日志中的第一个错误开始排查构建失败
  • 使用 OSSignposter 记录自定义性能区间
  • 使用条件断点调试循环或集合

参考资料

评论

支持 Markdown