Debugging Instruments

作者 dpearson26998d90fd121a26無授權條款1.1K 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫2 個月前更新

Debug iOS apps and profile performance using LLDB, the interactive Memory Graph Debugger, and Instruments. Use for crashes, retain-cycle inspection, hangs, build failures, and generic CPU, memory, energy, or network profiling. Use ios-memgraph-analysis for .memgraph capture, leaks CLI ownership paths, or persistent heap growth; use ios-ettrace-performance for ETTrace capture and JSON.

AI 產生的概覽

指導使用 LLDB、記憶體圖偵錯工具與 Instruments 排查 iOS 當機、洩漏、卡頓與建置失敗。

功能
為 iOS 應用程式提供互動式排查流程:LLDB 中斷點、運算式與監看點指令;記憶體圖偵錯工具與循環參照模式;搭配 signpost 與 Time Profiler 的卡頓診斷;建置失敗排查;以及 Instruments 範本選擇指南。也包含用於 CI 效能分析的 xctrace 命令列範例、常見錯誤說明與檢查清單。兩個參考檔案分別收錄完整的 LLDB 指令表與 Instruments 範本指南。
適用情境
適用於排查 iOS 當機、循環參照、主執行緒卡頓或建置與連結錯誤。也適合用 Instruments 分析 CPU、記憶體、能耗或網路表現。詳細的 .memgraph 命令列分析與 ETTrace 工作會轉交給其他專門技能。
執行需求
需要安裝 Xcode(含 LLDB、Instruments 與 xctrace 命令列工具),並準備目標應用程式的 Debug 或 Release 建置;部分診斷需在模擬器或連接的裝置上執行。此技能不附帶指令碼,僅為說明文件與參考資料。

Debugging and Instruments

Keep interactive graph and Instruments triage here. Route detailed .memgraph command-line ownership/growth analysis and ETTrace work to their focused skills.

Contents

LLDB Debugging

Start with a small, repeatable workflow:

  1. Reproduce in a Debug build and stop at the narrowest useful breakpoint.
  2. Inspect locals without executing code, then capture the current stack.
  3. Move to the relevant frame or thread and verify the failing state.
  4. Add a condition or watchpoint only when the bad transition is still unclear.
text
(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

Use v over po when you only need a local variable value — it does not execute code and cannot trigger side effects. Expression evaluation can execute or mutate program state, and hardware watchpoints are scarce, so use both deliberately.

Load references/lldb-patterns.md [blocked] for the complete inspection, breakpoint/logpoint, expression, watchpoint, thread navigation, and symbolic-breakpoint command tables.

Memory Debugging

Memory Graph Debugger Workflow

  1. Run the app in Debug configuration.
  2. Reproduce the suspected leak (navigate to a screen, then back).
  3. Tap the Memory Graph button in Xcode's debug bar.
  4. Look for purple warning icons — these indicate leaked objects.
  5. 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.

Common Retain Cycle Patterns

Closure capturing self strongly:

swift
// LEAK — closure holds strong reference to selfclass 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 cycleprotocol DataDelegate: AnyObject {    func didUpdate()}
class DataManager {    var delegate: DataDelegate?  // should be weak}
// FIXED — weak delegateclass DataManager {    weak var delegate: DataDelegate?}

Timer retaining target:

swift
// LEAK — Timer.scheduledTimer retains its targettimer = 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 and Leaks

  • 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: Detects leaked allocations, including isolated retain cycles the process can no longer reach. Run alongside Allocations for a complete picture.
  • Filter by your app's module name to exclude system allocations.

For leak or memory-growth triage, pair the tools: use Allocations Mark Generation before and after the reproduction step to prove retained growth, then use Memory Graph Debugger to inspect object ownership and Malloc Stack Logging to recover allocation call stacks.

Malloc Stack Logging

Enable in Scheme > Run > Diagnostics > Malloc Stack Logging. This records allocation backtraces so the Memory Graph Debugger, Allocations instrument, and exported .memgraph files can show where objects were created.

bash
# Inspect an exported memory graph from Xcode or Instrumentsleaks MyApp.memgraph

Hang Diagnostics

Identifying Main Thread Hangs

For discrete interactions, delays under 100 ms are rarely noticeable. Apple developer tools commonly report main-run-loop busy periods over 250 ms, but that reporting threshold is not a product target: a few hundred milliseconds can still feel unresponsive. Common detection tools:

  • Thread Checker (Xcode Diagnostics): warns about non-main-thread UI calls
  • Thread Performance Checker: reports priority inversions while debugging
  • On-device Hang Detection: Developer Settings reports hangs from device use
  • Time Profiler / CPU Profiler / Hitches: profile reproducible hangs
  • os_signpost and OSSignposter: mark intervals for Instruments
  • MetricKit hang diagnostics: production hang detection (see metrickit skill for HangDiagnostic and iOS 26 compatibility)
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

  1. Product > Profile (Cmd+I) to launch Instruments.
  2. Select the Time Profiler template.
  3. Record while reproducing the slow interaction.
  4. Focus on the main thread — sort by "Weight" to find hot paths.
  5. Check "Hide System Libraries" to see only your code.
  6. Double-click a heavy frame to jump to source.

Common Hang Causes

CauseSymptomFix
Synchronous I/O on main threadNetwork/file reads block UIMove to Task { } or background actor
Lock contentionMain thread waiting on a lock held by background workUse actors or reduce lock scope
Layout thrashingRepeated layoutSubviews callsBatch layout changes, avoid forced layout
JSON parsing large payloadsUI freezes during data loadParse on a background thread
Synchronous image decodingScroll jank on image-heavy listsUse AsyncImage or decode off main thread

Build Failure Triage

Reading Compiler Diagnostics

  • Start from the first error — subsequent errors are often cascading.
  • Search for the error code (e.g., error: cannot convert) in the build log.
  • Use Report Navigator (Cmd+9) for the full build log with timestamps.

SPM Dependency Resolution

text
# Common: version conflicterror: 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.swiftpmrm -rf .buildswift package resolve

Module Not Found / Linker Errors

ErrorCheck
No such module 'Foo'Target membership, import paths, framework search paths
Undefined symbolLinking phase missing framework, wrong architecture
duplicate symbolTwo targets define same symbol; check for ObjC naming collisions

Build settings to inspect first:

  • FRAMEWORK_SEARCH_PATHS
  • OTHER_LDFLAGS
  • SWIFT_INCLUDE_PATHS
  • BUILD_LIBRARY_FOR_DISTRIBUTION (for XCFrameworks)

Instruments Overview

Template Selection Guide

TemplateUse When
Time ProfilerCPU is high, UI feels slow, need to find hot code paths
AllocationsMemory grows over time, need to track object lifetimes
LeaksSuspect retain cycles or abandoned objects
NetworkInspecting HTTP request/response timing and payloads
SwiftUIProfiling view body evaluations and update frequency
Animation Hitches / Core Animation instrumentsFrame drops, hitches, blending, and commit/render work
Power ProfilerBattery drain, thermal pressure, background energy impact
File ActivityExcessive disk I/O, slow file operations
System TraceThread scheduling, syscalls, virtual memory faults

xctrace CLI for CI Profiling

bash
# Record a trace from the command linexcrun 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 analysisxcrun xctrace export --input profile.trace --xpath '/trace-toc/run/data/table'
# List available templatesxcrun xctrace list templates
# List connected devicesxcrun xctrace list devices

Use one --template per recording; add extra instruments with --instrument. Use xctrace in CI pipelines to catch performance regressions automatically. Compare exported metrics between builds.

Common Mistakes

DON'T: Use print() for debugging instead of os.Logger

Use Logger for levels, privacy metadata, and subsystem/category filtering; .debug remains in memory and is not persisted in release builds.

swift
// WRONG — unstructured and not filterable by subsystem/categoryprint("user tapped button, state: \(viewModel.state)")print("network response: \(data)")
// CORRECT — structured logging with Loggerimport 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)")

DON'T: Forget to enable Malloc Stack Logging before memory debugging

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

DON'T: Debug optimized code expecting full variable visibility

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)

DON'T: Stop on every loop iteration without conditional breakpoints

swift
// WRONG — breakpoint on line inside loop, stops 10,000 timesfor 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

DON'T: Ignore Thread Sanitizer warnings

Thread Sanitizer (TSan) warnings indicate data races that may only crash intermittently. Treat them as real bugs unless you have isolated a tool issue.

swift
// WRONG — ignoring TSan warning about concurrent accessvar cache: [String: Data] = [:]  // accessed from multiple threads
// CORRECT — protect shared mutable stateactor 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. For iOS, iPadOS, tvOS, visionOS, and watchOS apps, run TSan in Simulator; Apple documents device support only for 64-bit macOS apps.

Review Checklist

  • Using os.Logger instead of print() for diagnostic output
  • Malloc Stack Logging enabled before memory debugging sessions
  • Memory Graph Debugger checked after dismiss/dealloc flows
  • Delegates declared as weak var to prevent retain cycles
  • Closures stored as properties use [weak self] capture lists
  • Timers use closure-based API with [weak self]
  • Thread Sanitizer enabled in Simulator test schemes for race triage
  • 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
  • OSSignposter used for custom performance intervals
  • Conditional breakpoints used for loop/collection debugging

References

來源與署名

來源:dpearson2699/swift-ios-skills位於skills/debugging-instruments提交8d90fd1

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架

更多來自 dpearson2699/swift-ios-skills 的技能

Widgetkit

dpearson2699

指導實作、審查與改進 iOS、iPadOS、watchOS 與 CarPlay 上的 WidgetKit 小工具與控制項。

Software Development1.1K2 個月前更新

Weatherkit

dpearson2699

指導 iOS 開發者使用 WeatherService 取得 WeatherKit 預報、警報與署名資訊。

Software Development1.1K2 個月前更新

Vision Framework

dpearson2699

Implement computer vision features including text recognition (OCR), face detection, barcode scanning, image segmentation, object tracking, and document scanning in iOS apps. Covers both the modern Swift-native Vision API (iOS 18+) and legacy VNRequest patterns, VisionKit DataScannerViewController for live camera scanning, and CoreMLRequest/VNCoreMLRequest for custom model inference. Use when adding OCR, barcode scanning, face detection, or custom Core ML model inference with Vision.

待分類1.1K2 個月前更新

Tipkit

dpearson2699

Implement and review Apple TipKit feature-discovery UI for iOS 17+ apps. Use when adding or auditing in-app tips, contextual help, coach marks, Tip, TipView, popoverTip, rules, events, actions, display frequency, testing overrides, reusable tip identifiers, or iOS 18+ TipGroup and CloudKit tip sync; avoid for generic SwiftUI navigation or layout outside tip presentation.

待分類1.1K2 個月前更新

Tabletopkit

dpearson2699

指導使用 TabletopKit 在 visionOS 上打造多人空間桌遊,涵蓋棋具、座位、動作與 RealityKit 算繪。

Software Development1.1K2 個月前更新

Swiftui Webkit

dpearson2699

指導在 iOS 26 及更新版本的 SwiftUI App 中使用 WebKit for SwiftUI 嵌入與控制網頁內容。

Software Development1.1K2 個月前更新