/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,
$ npx -y skills add dpearson2699/swift-ios-skills --skill debugging-instruments --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/debugging-instruments
Context preview
The summary Claude sees to decide when to auto-load this skill.
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,
SKILL.md
debugging-instruments.SKILL.mdname: debugging-instruments
description: "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."
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](#lldb-debugging)
- [Memory Debugging](#memory-debugging)
- [Hang Diagnostics](#hang-diagnostics)
- [Build Failure Triage](#build-failure-triage)
- [Instruments Overview](#instruments-overview)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)
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.
(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](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
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:**
// 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.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)
import os
let signposter = OSSignposter(subsystem: "com.example.app", category: "DataLoad")
func loadData() async {
let state = signposter.beginInterval("loadData")
let result = await fRead more
name: debugging-instruments description: "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."
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](#lldb-debugging)
- [Memory Debugging](#memory-debugging)
- [Hang Diagnostics](#hang-diagnostics)
- [Build Failure Triage](#build-failure-triage)
- [Instruments Overview](#instruments-overview)
- [Common Mistakes](#common-mistakes)
- [Review Checklist](#review-checklist)
- [References](#references)
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.
(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](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
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:**
// 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.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)
import os
let signposter = OSSignposter(subsystem: "com.example.app", category: "DataLoad")
func loadData() async {
let state = signposter.beginInterval("loadData")
let result = await f86 agent skills optimized for iOS 26+ development with Swift 6.3 and modern Apple frameworks.
Repo: dpearson2699/swift-ios-skills
Other skills on swift-ios-skills.
- /accessorysetupkit
Discover and configure Bluetooth and Wi-Fi accessories using AccessorySetupKit. Use when presenting a privacy-preserving accessory picker, defining discovery descriptors for BLE or Wi-Fi devices, handling accessory session events, migrating from CoreBluetooth permission-based
Open skill - /activitykit
Implement, review, or improve Live Activities and Dynamic Island experiences in iOS apps using ActivityKit. Use when building real-time updating widgets for the Lock Screen and Dynamic Island — delivery tracking, sports scores, ride-sharing status, workout timers, media
Open skill - /adattributionkit
Measure ad effectiveness with privacy-preserving attribution using AdAttributionKit. Use when registering ad impressions, handling attribution postbacks, updating conversion values, implementing re-engagement attribution, configuring publisher or advertiser apps, or replacing
Open skill - /alarmkit
Implement AlarmKit alarms and countdown timers for iOS and iPadOS with Lock Screen, Dynamic Island, StandBy, and paired Apple Watch system UI. Covers AlarmManager scheduling, AlarmAttributes and AlarmPresentation, system Stop and AlarmButton secondary actions, authorization,
Open skill - /app-clips
Build iOS App Clips with invocation URLs, App Clip Codes, NFC, QR codes, Safari banners, Maps, Messages, target setup, App Store Connect experiences, size/capability constraints, NSUserActivity routing, SKOverlay promotion, App Group/keychain handoff, ephemeral notifications,
Open skill - /app-intents
Implement App Intents for Siri, Shortcuts, Spotlight, widgets, Control Center, and Apple Intelligence on iOS. Covers AppIntent actions, AppEntity and EntityQuery models, AppShortcutsProvider phrases, IndexedEntity Spotlight indexing, WidgetConfigurationIntent, SnippetIntent, and
Open skill

