Debugging Instruments
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.
- Skill ID
- dpearson2699/swift-ios-skills/debugging-instruments
- Publisher
- dpearson2699
- Repository
- swift-ios-skills
- Installs
- 0
- Files
- 4
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
dpearson2699/swift-ios-skills/debugging-instrumentsInstalls these files- SKILL.md
- evals/evals.json
- references/instruments-guide.md
- references/lldb-patterns.md
What this skill tells the agent
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
- Memory Debugging
- Hang Diagnostics
- Build Failure Triage
- Instruments Overview
- Common Mistakes
- Review Checklist
- References
LLDB Debugging
Start with a small, repeatable workflow:
- Reproduce in a Debug build and stop at the narrowest useful breakpoint.
- Inspect locals without executing code, then capture the current stack.
- Move to the relevant frame or thread and verify the failing state.
- Add a condition or watchpoint only when the bad transition is still unclear.
(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 writeUse 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 for the complete inspection, breakpoint/logpoint, expression, watchpoint, thread navigation, and symbolic-breakpoint command tables.
Memory Debugging
Memory Graph Debugger Workflow
- 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.
Common Retain Cycle Patterns
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()
}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.
# Inspect an exported memory graph from Xcode or Instruments
leaks MyApp.memgraphHang 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
metrickitskill forHangDiagnosticand iOS 26 compatibility)
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
- 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.
Common Hang Causes
| 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 |
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
# 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 resolveModule Not Found / Linker Errors
| 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)
Instruments Overview
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 |
More from dpearson2699
Use this skill in a project
Open any RiverX project, search the Skills panel for dpearson2699/swift-ios-skills/debugging-instruments, and install it in one click.
