Back to skills
SkillHub ClubBuild MobileFull StackMobile

swift-diagnostics

Use when debugging NavigationStack issues (not responding, unexpected pops, crashes), build failures (SPM resolution, "No such module", hanging builds), or memory problems (retain cycles, leaks, deinit not called). Systematic diagnostic workflows for iOS/macOS.

Packaged view

This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.

Stars
168
Hot score
96
Updated
March 20, 2026
Overall rating
C3.8
Composite score
3.8
Best-practice grade
B73.6

Install command

npx @skill-hub/cli install johnrogers-claude-swift-engineering-swift-diagnostics

Repository

johnrogers/claude-swift-engineering

Skill path: plugins/swift-engineering/skills/swift-diagnostics

Use when debugging NavigationStack issues (not responding, unexpected pops, crashes), build failures (SPM resolution, "No such module", hanging builds), or memory problems (retain cycles, leaks, deinit not called). Systematic diagnostic workflows for iOS/macOS.

Open repository

Best for

Primary workflow: Build Mobile.

Technical facets: Full Stack, Mobile.

Target audience: everyone.

License: Unknown.

Original source

Catalog source: SkillHub Club.

Repository owner: johnrogers.

This is still a mirrored public skill entry. Review the repository before installing into production workflows.

What it helps with

  • Install swift-diagnostics into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
  • Review https://github.com/johnrogers/claude-swift-engineering before adding swift-diagnostics to shared team environments
  • Use swift-diagnostics for development workflows

Works across

Claude CodeCodex CLIGemini CLIOpenCode

Favorites: 0.

Sub-skills: 0.

Aggregator: No.

Original source / Raw SKILL.md

---
name: swift-diagnostics
description: Use when debugging NavigationStack issues (not responding, unexpected pops, crashes), build failures (SPM resolution, "No such module", hanging builds), or memory problems (retain cycles, leaks, deinit not called). Systematic diagnostic workflows for iOS/macOS.
---

# Swift Diagnostics

Systematic debugging workflows for iOS/macOS development. These patterns help identify root causes in minutes rather than hours by following structured diagnostic approaches.

## Reference Loading Guide

**ALWAYS load reference files if there is even a small chance the content may be required.** It's better to have the context than to miss a pattern or make a mistake.

| Reference | Load When |
|-----------|-----------|
| **[Navigation](references/navigation.md)** | NavigationStack not responding, unexpected pops, deep link failures |
| **[Build Issues](references/build-issues.md)** | SPM resolution, "No such module", dependency conflicts |
| **[Memory](references/memory.md)** | Retain cycles, memory growth, deinit not called |
| **[Build Performance](references/build-performance.md)** | Slow builds, Derived Data issues, Xcode hangs |
| **[Xcode Debugging](references/xcode-debugging.md)** | LLDB commands, breakpoints, view debugging |

## Core Workflow

1. **Identify symptom category** - Navigation, build, memory, or performance
2. **Load the relevant reference** - Each has diagnostic decision trees
3. **Run mandatory first checks** - Before changing any code
4. **Follow the decision tree** - Reach diagnosis in 2-5 minutes
5. **Apply fix and verify** - One fix at a time, test each

## Key Principle

80% of "mysterious" issues stem from predictable patterns:
- Navigation: Path state management or destination placement
- Build: Stale caches or dependency resolution
- Memory: Timer/observer leaks or closure captures
- Performance: Environment problems, not code bugs

Diagnose systematically. Never guess.

## Common Mistakes

1. **Skipping mandatory first checks** — Jumping straight to code changes before running diagnostics (clean build, restart simulator, restart Xcode) means you'll chase ghosts. Always start with the mandatory checks.

2. **Changing multiple things at once** — "Let me delete DerivedData AND restart simulator AND kill Xcode" means you can't isolate which fix actually worked. Change one variable at a time.

3. **Assuming you know the cause** — "NavigationStack stopped working, must be my reducer" — actually it was stale DerivedData. Diagnostic trees prevent assumptions. Follow the tree, don't guess.

4. **Missing memory basics** — Calling `deinit` not being called is a retain cycle, but beginners often blame architecture. Use Instruments to verify leaks before refactoring. Data, not intuition.

5. **Not isolating the problem** — Testing with your whole app complicates diagnosis. Create a minimal reproducible example with just the problematic feature. Isolation reveals root causes.


---

## Referenced Files

> The following files are referenced in this skill and included for context.

### references/navigation.md

```markdown
# Navigation Diagnostics

Systematic debugging for NavigationStack issues. 85% of navigation problems stem from path state management, view identity, or destination placement.

## Diagnostic Decision Table

| Observation | Diagnosis | Next Step |
|-------------|-----------|-----------|
| onChange never fires on tap | NavigationLink not in NavigationStack | Check view hierarchy |
| onChange fires but view doesn't push | navigationDestination not found | Check destination placement |
| Pushes then immediately pops | View identity issue or path reset | Check @State location |
| Path changes unexpectedly | External code modifying path | Add logging to find source |
| Deep link doesn't navigate | Timing issue or wrong thread | Check MainActor isolation |
| State lost on tab switch | NavigationStack shared across tabs | Use separate stacks per tab |

## Mandatory First Checks

Run these BEFORE changing code:

```swift
// 1. Add NavigationPath logging
NavigationStack(path: $path) {
    RootView()
        .onChange(of: path.count) { oldCount, newCount in
            print("Path changed: \(oldCount) -> \(newCount)")
        }
}

// 2. Verify navigationDestination is evaluated
.navigationDestination(for: Recipe.self) { recipe in
    let _ = print("Destination for: \(recipe.name)")
    RecipeDetail(recipe: recipe)
}

// 3. Test minimal case in isolation
NavigationStack {
    NavigationLink("Test", value: "test")
        .navigationDestination(for: String.self) { str in
            Text("Pushed: \(str)")
        }
}
```

## Decision Tree

```
Navigation problem?
|-- Link tap does nothing?
|   |-- onChange fires? -> Check navigationDestination placement
|   |-- onChange silent? -> Link outside NavigationStack
|
|-- Unexpected pop back?
|   |-- Immediate after push? -> Path recreated (check @State location)
|   |-- Random timing? -> External code modifying path
|
|-- Deep link fails?
|   |-- URL received? -> Check MainActor for path.append
|   |-- URL not received? -> Check URL scheme in Info.plist
|
|-- State lost on tab switch?
    |-- Same path for all tabs? -> Each tab needs own NavigationStack
```

## Common Patterns

### Pattern 1: Link Outside NavigationStack

```swift
// WRONG - Link outside stack
VStack {
    NavigationLink("Go", value: "test")  // Won't work
    NavigationStack {
        Text("Root")
    }
}

// CORRECT - Link inside stack
NavigationStack {
    VStack {
        NavigationLink("Go", value: "test")
        Text("Root")
    }
    .navigationDestination(for: String.self) { Text($0) }
}
```

### Pattern 2: Destination in Lazy Container

```swift
// WRONG - Destination may not be loaded
LazyVStack {
    ForEach(items) { item in
        NavigationLink(item.name, value: item)
            .navigationDestination(for: Item.self) { /* ... */ }
    }
}

// CORRECT - Destination outside lazy container
LazyVStack {
    ForEach(items) { item in
        NavigationLink(item.name, value: item)
    }
}
.navigationDestination(for: Item.self) { item in
    ItemDetail(item: item)
}
```

### Pattern 3: Path Recreated Every Render

```swift
// WRONG - Path reset on every body evaluation
struct ContentView: View {
    var body: some View {
        let path = NavigationPath()  // Recreated!
        NavigationStack(path: .constant(path)) { /* ... */ }
    }
}

// CORRECT - @State persists across renders
struct ContentView: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) { /* ... */ }
    }
}
```

### Pattern 4: Path Modified Off MainActor

```swift
// WRONG - May fail silently
func loadAndNavigate() async {
    let recipe = await fetchRecipe()
    path.append(recipe)  // Not on MainActor
}

// CORRECT - Explicit MainActor
@MainActor
func loadAndNavigate() async {
    let recipe = await fetchRecipe()
    path.append(recipe)
}
```

### Pattern 5: Deep Link Timing

```swift
// WRONG - NavigationStack may not exist yet
.onOpenURL { url in
    handleDeepLink(url)  // Too early on cold start
}

// CORRECT - Queue until ready
@State private var pendingDeepLink: URL?
@State private var isReady = false

var body: some View {
    NavigationStack(path: $path) {
        RootView()
            .onAppear {
                isReady = true
                if let url = pendingDeepLink {
                    handleDeepLink(url)
                    pendingDeepLink = nil
                }
            }
    }
    .onOpenURL { url in
        if isReady {
            handleDeepLink(url)
        } else {
            pendingDeepLink = url
        }
    }
}
```

### Pattern 6: Shared NavigationStack Across Tabs

```swift
// WRONG - All tabs share navigation state
NavigationStack(path: $path) {
    TabView {
        Tab("Home") { HomeView() }
        Tab("Settings") { SettingsView() }
    }
}

// CORRECT - Each tab has own stack
TabView {
    Tab("Home", systemImage: "house") {
        NavigationStack {
            HomeView()
        }
    }
    Tab("Settings", systemImage: "gear") {
        NavigationStack {
            SettingsView()
        }
    }
}
```

## Type Mismatch Debugging

```swift
// Check: Value type must exactly match destination type
NavigationLink(recipe.name, value: recipe)  // Recipe type

// This won't work if destination is for Recipe.ID
.navigationDestination(for: Recipe.ID.self) { id in  // Wrong!
    RecipeDetail(id: id)
}

// Types must match
.navigationDestination(for: Recipe.self) { recipe in  // Correct
    RecipeDetail(recipe: recipe)
}
```

## Verification Checklist

After applying fix:
- [ ] onChange(of: path) fires when expected
- [ ] Destination print statement executes
- [ ] Navigation persists (doesn't pop back)
- [ ] Works on cold start (deep links)
- [ ] State preserved across tab switches

```

### references/build-issues.md

```markdown
# Build Issues Diagnostics

Systematic debugging for SPM resolution, "No such module", and dependency conflicts. 80% of persistent build failures are dependency resolution issues, not code bugs.

## Diagnostic Decision Table

| Error | Likely Cause | First Check |
|-------|--------------|-------------|
| "No such module" after adding package | SPM cache stale | Clear package caches |
| "Multiple commands produce" | Duplicate file in targets | Check target membership |
| Build works locally, fails on CI | Environment/cache difference | Compare Podfile.lock |
| SPM resolution hangs | Package cache corruption | Delete .build and DerivedData |
| Framework version conflicts | Transitive dependency issue | Check Package.resolved |

## Mandatory First Checks

```bash
# 1. Check Derived Data size (>10GB = stale)
du -sh ~/Library/Developer/Xcode/DerivedData

# 2. Check for zombie xcodebuild processes
ps aux | grep xcodebuild | grep -v grep

# 3. List available schemes
xcodebuild -list
```

## Decision Tree

```
Build failing?
|-- "No such module XYZ"?
|   |-- After adding SPM package? -> Clean + reset package caches
|   |-- After pod install? -> Check Podfile.lock conflicts
|   |-- Framework not found? -> Check FRAMEWORK_SEARCH_PATHS
|
|-- "Multiple commands produce"?
|   |-- Duplicate files in target membership -> Check File Inspector
|
|-- SPM resolution hangs?
|   |-- Clear package caches + Derived Data
|
|-- Version conflicts?
    |-- Use dependency resolution strategies below
```

## Quick Fixes

### SPM Package Not Found

```bash
# Nuclear clean
rm -rf ~/Library/Developer/Xcode/DerivedData
rm -rf ~/Library/Caches/org.swift.swiftpm

# Reset packages in project
xcodebuild -resolvePackageDependencies

# Clean build
xcodebuild clean build -scheme YourScheme
```

### CocoaPods Conflicts

```bash
# Check what versions were installed
cat Podfile.lock | grep -A 2 "PODS:"

# Clean reinstall
rm -rf Pods/
rm Podfile.lock
pod install

# Always open workspace (not project)
open YourApp.xcworkspace
```

### Multiple Commands Produce Error

1. Open Xcode
2. Select file in navigator
3. File Inspector > Target Membership
4. Uncheck duplicate targets
5. Or: Build Phases > Copy Bundle Resources > remove duplicates

### Framework Search Paths

```bash
# Show all build settings
xcodebuild -showBuildSettings -scheme YourScheme | grep FRAMEWORK_SEARCH_PATHS
```

Fix in Xcode:
1. Target > Build Settings
2. Search "Framework Search Paths"
3. Add: `$(PROJECT_DIR)/Frameworks` (recursive)

## Dependency Resolution Strategies

### Strategy 1: Lock to Specific Versions

```ruby
# Podfile - exact versions
pod 'Alamofire', '5.8.0'
pod 'SwiftyJSON', '~> 5.0.0'  # Any 5.0.x
```

```swift
// Package.swift - exact versions
.package(url: "...", exact: "1.2.3")
```

### Strategy 2: Use Version Ranges

```swift
// Package.swift
.package(url: "...", from: "1.2.0")              // 1.2.0 and higher
.package(url: "...", .upToNextMajor(from: "1.0.0"))  // 1.x.x but not 2.0
```

### Strategy 3: Reset SPM Resolution

```bash
# Clear package caches
rm -rf .build
rm Package.resolved

# Re-resolve
swift package resolve
```

## Debug vs Release Differences

```bash
# Compare configurations
xcodebuild -showBuildSettings -configuration Debug > debug.txt
xcodebuild -showBuildSettings -configuration Release > release.txt
diff debug.txt release.txt
```

Common culprits:
- SWIFT_OPTIMIZATION_LEVEL (-Onone vs -O)
- ENABLE_TESTABILITY (YES in Debug, NO in Release)
- DEBUG preprocessor flag

## Command Reference

```bash
# CocoaPods
pod install                    # Install dependencies
pod update                     # Update to latest versions
pod outdated                   # Check for updates
pod deintegrate                # Remove CocoaPods from project

# Swift Package Manager
swift package resolve          # Resolve dependencies
swift package update           # Update dependencies
swift package show-dependencies # Show dependency tree
xcodebuild -resolvePackageDependencies  # Xcode's SPM resolve

# Xcode Build
xcodebuild clean               # Clean build folder
xcodebuild -list               # List schemes and targets
xcodebuild -showBuildSettings  # Show all build settings
```

## Common Mistakes

### Not Committing Lockfiles
```bash
# BAD: .gitignore includes lockfiles
Podfile.lock
Package.resolved

# These should be committed for reproducible builds
```

### Using "Latest" Version
```ruby
# BAD: No version specified
pod 'Alamofire'  # Breaking changes when updated

# GOOD: Explicit version
pod 'Alamofire', '~> 5.8'
```

### Opening Project Instead of Workspace
```bash
# BAD (with CocoaPods)
open YourApp.xcodeproj

# GOOD
open YourApp.xcworkspace
```

## Verification Checklist

After applying fix:
- [ ] Build succeeds with clean Derived Data
- [ ] All dependencies resolve to expected versions
- [ ] Both Debug and Release configurations build
- [ ] CI builds match local builds
- [ ] Lockfiles committed to source control

```

### references/memory.md

```markdown
# Memory Diagnostics

Systematic debugging for retain cycles, memory leaks, and deallocation issues. 90% of memory leaks follow 3 patterns: timer leaks, observer leaks, and closure captures.

## Diagnostic Decision Table

| Symptom | Likely Cause | Diagnostic Tool |
|---------|--------------|-----------------|
| Memory grows 50MB -> 100MB -> 200MB | Retain cycle or timer leak | Memory Graph Debugger |
| App crashes after 10-15 minutes | Progressive memory leak | Instruments Allocations |
| deinit never called | Strong reference cycle | Memory Graph Debugger |
| Memory spike on specific action | Collection/closure leak | Allocations + filtering |
| Memory stays high after dismissing view | ViewController not deallocating | Add deinit logging |

## Mandatory First Checks

```swift
// 1. Add deinit logging to suspected class
class PlayerViewModel: ObservableObject {
    deinit {
        print("PlayerViewModel deallocated")
    }
}

// 2. Test deallocation
var vm: PlayerViewModel? = PlayerViewModel()
vm = nil  // Should print "deallocated"
```

```bash
# 3. Check device logs for memory warnings
# Connect device, open Xcode Console (Cmd+Shift+2)
# Look for: "Memory pressure critical", "Jetsam killed"

# 4. Check memory baseline
# Xcode > Product > Profile > Memory
# Perform action 5 times, check if memory keeps growing
```

## Decision Tree

```
Memory growing?
|-- Progressive growth every minute?
|   |-- Timer or notification leak -> Check Pattern 1 & 2
|
|-- Spike when action performed?
|   |-- Check if operation runs multiple times
|   |-- Spike then flat? -> Probably normal caching
|
|-- deinit not called?
|   |-- Use Memory Graph Debugger
|   |-- Look for purple/red circles with warning badge
|
|-- Can't tell from inspection?
    |-- Use Instruments > Allocations
    |-- Track object counts over time
```

## Common Leak Patterns

### Pattern 1: Timer Leaks (Most Common)

```swift
// WRONG - Timer never invalidated
class PlayerViewModel: ObservableObject {
    private var timer: Timer?

    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.update()
        }
        // Timer never stopped -> keeps firing forever
    }
}

// CORRECT - Invalidate in deinit
class PlayerViewModel: ObservableObject {
    private var timer: Timer?

    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.update()
        }
    }

    func stop() {
        timer?.invalidate()
        timer = nil
    }

    deinit {
        timer?.invalidate()
        timer = nil
    }
}
```

### Pattern 2: Observer Leaks

```swift
// WRONG - Observer never removed
class PlayerViewModel: ObservableObject {
    init() {
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(handleChange),
            name: .audioRouteChanged,
            object: nil
        )
    }
}

// CORRECT - Use Combine (auto-cleanup)
class PlayerViewModel: ObservableObject {
    private var cancellables = Set<AnyCancellable>()

    init() {
        NotificationCenter.default.publisher(for: .audioRouteChanged)
            .sink { [weak self] _ in
                self?.handleChange()
            }
            .store(in: &cancellables)
    }
}
```

### Pattern 3: Closure Capture Leaks

```swift
// WRONG - Closure captures self strongly
class ViewController: UIViewController {
    var callbacks: [() -> Void] = []

    func addCallback() {
        callbacks.append {
            self.refresh()  // Strong capture
        }
    }
}

// CORRECT - Use weak self
class ViewController: UIViewController {
    var callbacks: [() -> Void] = []

    func addCallback() {
        callbacks.append { [weak self] in
            self?.refresh()
        }
    }

    deinit {
        callbacks.removeAll()
    }
}
```

### Pattern 4: Delegate Cycles

```swift
// WRONG - Strong delegate reference
class Player {
    var delegate: PlayerDelegate?  // Strong reference
}

class Controller: PlayerDelegate {
    var player: Player?

    init() {
        player = Player()
        player?.delegate = self  // Creates cycle
    }
}

// CORRECT - Weak delegate
class Player {
    weak var delegate: PlayerDelegate?
}
```

## Using Memory Graph Debugger

1. Run app in Xcode simulator
2. Debug > Memory Graph Debugger (or toolbar icon)
3. Wait for graph to generate (5-10 seconds)
4. Look for purple/red circles with warning badge
5. Click to see retain cycle chain

Example output:
```
PlayerViewModel
  ^ strongRef from: progressTimer
    ^ strongRef from: TimerClosure
      ^ CYCLE DETECTED
```

## Using Instruments Allocations

1. Product > Profile (Cmd+I)
2. Select "Allocations" template
3. Perform action 5-10 times
4. Check: Does memory line keep going UP?
   - YES -> Leak confirmed
   - NO -> Probably not a leak

```
Time -->
Memory
   |     -------- <- Memory keeps growing (LEAK)
   |    /
   |   /
   |  /
   +----------

vs normal:

   |  -------- <- Memory plateaus (OK)
   | /
   |/
   +----------
```

## PhotoKit Request Leaks

```swift
// WRONG - Requests accumulate without cancellation
func loadImage(asset: PHAsset) {
    imageManager.requestImage(for: asset, ...) { image, _ in
        self.imageView.image = image
    }
}

// CORRECT - Cancel in prepareForReuse
class PhotoCell: UICollectionViewCell {
    private var requestID: PHImageRequestID = PHInvalidImageRequestID

    func configure(asset: PHAsset) {
        if requestID != PHInvalidImageRequestID {
            PHImageManager.default().cancelImageRequest(requestID)
        }

        requestID = imageManager.requestImage(for: asset, ...) { [weak self] image, _ in
            self?.imageView.image = image
        }
    }

    override func prepareForReuse() {
        super.prepareForReuse()
        if requestID != PHInvalidImageRequestID {
            PHImageManager.default().cancelImageRequest(requestID)
            requestID = PHInvalidImageRequestID
        }
    }
}
```

## Quick Reference

| Leak Type | Detection | Fix |
|-----------|-----------|-----|
| Timer | deinit not called | invalidate() in deinit |
| Observer | Memory grows steadily | Use Combine + cancellables |
| Closure | Memory Graph shows cycle | [weak self] capture |
| Delegate | Both objects stay alive | weak var delegate |
| Image requests | Memory spikes on scroll | Cancel in prepareForReuse |

## Verification Checklist

After applying fix:
- [ ] deinit prints when expected
- [ ] Memory stays flat in Instruments
- [ ] No purple/red warnings in Memory Graph
- [ ] App doesn't crash after extended use
- [ ] Memory drops under simulated pressure (Xcode > Debug > Simulate Memory Warning)

```

### references/build-performance.md

```markdown
# Build Performance Diagnostics

Systematic debugging for slow builds, Derived Data issues, and Xcode hangs. 80% of "mysterious" build issues are environment problems, not code bugs.

## Diagnostic Decision Table

| Symptom | Likely Cause | First Check |
|---------|--------------|-------------|
| Build takes 10+ minutes | Stale Derived Data | Check DerivedData size |
| "Build succeeded" but old code runs | Cached build artifact | Delete Derived Data |
| Xcode beach balls during build | Zombie xcodebuild processes | Check process list |
| Simulator stuck at splash | Simulator in bad state | simctl list devices |
| Intermittent build failures | Environment corruption | Full clean rebuild |

## Mandatory First Checks

```bash
# 1. Check for zombie processes
ps aux | grep -E "xcodebuild|Simulator" | grep -v grep

# 2. Check Derived Data size (>10GB = stale)
du -sh ~/Library/Developer/Xcode/DerivedData

# 3. Check simulator states
xcrun simctl list devices | grep -E "Booted|Booting|Shutting Down"
```

What these tell you:
- **0 processes + small DerivedData + no stuck sims** -> Environment clean
- **10+ processes OR >10GB DerivedData OR simulators stuck** -> Clean first

## Decision Tree

```
Build/performance problem?
|-- BUILD FAILED with no details?
|   |-- Clean Derived Data -> rebuild
|
|-- Build succeeds but old code executes?
|   |-- Delete Derived Data -> rebuild (2-5 min fix)
|
|-- Build intermittent (sometimes succeeds/fails)?
|   |-- Clean Derived Data -> rebuild
|
|-- Xcode hangs during build?
|   |-- Check for zombie xcodebuild processes
|   |-- killall -9 xcodebuild
|
|-- "Unable to boot simulator"?
|   |-- xcrun simctl shutdown all
|   |-- xcrun simctl erase <device-uuid>
|
|-- Tests hang indefinitely?
    |-- Check simctl list -> reboot simulator
```

## Quick Fixes

### Clean Derived Data

```bash
# Delete all Derived Data
rm -rf ~/Library/Developer/Xcode/DerivedData/*

# Also clean project-specific build folders
rm -rf .build/ build/

# Clean and rebuild
xcodebuild clean -scheme YourScheme
xcodebuild build -scheme YourScheme \
  -destination 'platform=iOS Simulator,name=iPhone 16'
```

### Kill Zombie Processes

```bash
# Kill all xcodebuild processes
killall -9 xcodebuild

# Verify they're gone
ps aux | grep xcodebuild | grep -v grep

# Kill Simulator if stuck
killall -9 Simulator
```

### Fix Simulator Issues

```bash
# Shutdown all simulators
xcrun simctl shutdown all

# If simctl fails, force quit first
killall -9 Simulator
xcrun simctl shutdown all

# List simulators to find problematic one
xcrun simctl list devices

# Erase specific simulator
xcrun simctl erase <device-uuid>

# Boot fresh simulator
xcrun simctl boot "iPhone 16 Pro"
```

### Reset SPM Caches

```bash
# Clear SPM caches
rm -rf ~/Library/Caches/org.swift.swiftpm

# Reset package dependencies
xcodebuild -resolvePackageDependencies

# Full clean rebuild
rm -rf ~/Library/Developer/Xcode/DerivedData/*
xcodebuild clean build -scheme YourScheme
```

## Identifying Build Time Hotspots

```bash
# Enable build timing
defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

# Restart Xcode, build, check activity log
# Xcode > Report Navigator > Build log
```

## Xcode Build Commands

```bash
# List available schemes
xcodebuild -list

# Show build settings
xcodebuild -showBuildSettings -scheme YourScheme

# Verbose build (more diagnostics)
xcodebuild -verbose build -scheme YourScheme

# Build for testing only (faster iteration)
xcodebuild build-for-testing -scheme YourScheme

# Run tests without rebuilding
xcodebuild test-without-building -scheme YourScheme \
  -destination 'platform=iOS Simulator,name=iPhone 16'

# Run specific test only
xcodebuild test -scheme YourScheme \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -only-testing:YourTests/SpecificTestClass
```

## Crash Log Analysis

```bash
# Find recent crashes
ls -lt ~/Library/Logs/DiagnosticReports/*.crash | head -5

# View crash log
cat ~/Library/Logs/DiagnosticReports/YourApp-*.crash | head -100

# Symbolicate address (if you have .dSYM)
atos -o YourApp.app.dSYM/Contents/Resources/DWARF/YourApp \
  -arch arm64 0x<address>
```

## Environment Reset (Nuclear Option)

When nothing else works:

```bash
# 1. Quit Xcode
osascript -e 'quit app "Xcode"'

# 2. Kill all related processes
killall -9 xcodebuild Simulator

# 3. Clean all caches
rm -rf ~/Library/Developer/Xcode/DerivedData/*
rm -rf ~/Library/Caches/org.swift.swiftpm
rm -rf .build/ build/

# 4. Reset simulators
xcrun simctl shutdown all
xcrun simctl erase all

# 5. Reopen Xcode
open -a Xcode YourProject.xcodeproj
```

## Common Error Patterns

| Error | Fix |
|-------|-----|
| BUILD FAILED (no details) | Delete Derived Data |
| Unable to boot simulator | `xcrun simctl erase <uuid>` |
| No such module | Clean + delete Derived Data |
| Tests hang | Check simctl list, reboot simulator |
| Stale code executing | Delete Derived Data |

## Common Mistakes

- **Debugging code before checking environment** - Always run mandatory steps first
- **Ignoring simulator states** - "Booting" can hang 10+ minutes
- **Assuming git changes caused problem** - Derived Data caches old builds
- **Running full test suite when one test fails** - Use `-only-testing`

## Verification Checklist

After applying fix:
- [ ] No zombie xcodebuild processes
- [ ] Derived Data under 5GB
- [ ] Simulators all in Shutdown state
- [ ] Clean build succeeds
- [ ] Correct code executes (not cached)
- [ ] Tests pass consistently

```

### references/xcode-debugging.md

```markdown
# Xcode Debugging Reference

LLDB commands, breakpoints, and view debugging techniques for iOS/macOS development.

## LLDB Quick Reference

### Basic Commands

```lldb
# Print variable
po myVariable
p myVariable

# Print with format
p/x myInt          # Hexadecimal
p/t myInt          # Binary
p/d myInt          # Decimal

# Print object description
po self
po myArray

# Expression evaluation
expr myVariable = 5
expr self.isLoading = true
```

### Inspecting Objects

```lldb
# Print all properties
po self.debugDescription

# Print specific property
po self.viewModel.state

# Print array contents
po myArray.map { $0.name }

# Print dictionary
po myDict.keys
po myDict["key"]
```

### SwiftUI Debugging

```lldb
# Print view hierarchy (from any breakpoint in a View)
po Self._printChanges()

# Check @State value
po _myStateVariable.wrappedValue

# Check @Binding
po _myBinding.wrappedValue
```

### Memory Inspection

```lldb
# Check memory address
p unsafeBitCast(myObject, to: Int.self)

# Check reference count
po CFGetRetainCount(myObject as CFTypeRef)

# Print type
po type(of: myObject)
```

## Breakpoint Techniques

### Conditional Breakpoints

1. Set breakpoint (click line number gutter)
2. Right-click breakpoint > Edit Breakpoint
3. Add condition:
   - `myVariable == 5`
   - `myArray.count > 10`
   - `self.state == .loading`

### Action Breakpoints

Execute code without stopping:

1. Edit Breakpoint
2. Add Action > Debugger Command
3. Enter: `po "Value is: \(myVariable)"`
4. Check "Automatically continue"

### Exception Breakpoints

Catch crashes before they happen:

1. Debug Navigator > + button
2. Add Exception Breakpoint
3. Choose: All Exceptions or Objective-C only

### Symbolic Breakpoints

Break on any method call:

1. Debug Navigator > + button
2. Add Symbolic Breakpoint
3. Symbol: `-[UIViewController viewDidLoad]`
4. Or: `UIApplication.shared`

## View Debugging

### Debug View Hierarchy

1. Run app
2. Debug > View Debugging > Capture View Hierarchy
3. Or click cube icon in debug toolbar

### What to Look For

- **Overlapping views** - Views stacked unexpectedly
- **Constraint issues** - Ambiguous layout warnings
- **Hidden views** - Views with alpha 0 or isHidden true
- **Off-screen content** - Views positioned outside bounds

### Runtime Attribute Inspection

In the view debugger:
1. Select view in 3D hierarchy
2. Object Inspector shows all properties
3. Check frame, bounds, constraints

### View Debugging Commands

```lldb
# Print view hierarchy
po self.view.recursiveDescription()

# Print responder chain
po self.responderChain()

# Highlight view (in simulator)
expr self.view.layer.borderWidth = 2
expr self.view.layer.borderColor = UIColor.red.cgColor
```

## Network Debugging

### Print Network Requests

```swift
// Add to URLSession configuration
#if DEBUG
URLSession.shared.configuration.protocolClasses?.insert(NetworkLogger.self, at: 0)
#endif
```

### LLDB Network Inspection

```lldb
# Print URL request
po request.url
po request.httpMethod
po String(data: request.httpBody ?? Data(), encoding: .utf8)

# Print response
po response.statusCode
po String(data: responseData, encoding: .utf8)
```

## Thread Debugging

### Check Current Thread

```lldb
# Print current thread
thread info

# Print all threads
thread list

# Print backtrace
bt
bt all
```

### Main Thread Checker

Enable in scheme:
1. Edit Scheme > Run > Diagnostics
2. Check "Main Thread Checker"

Catches UI updates from background threads.

## Performance Debugging

### Time Profiler in LLDB

```lldb
# Measure execution time
expr let start = CFAbsoluteTimeGetCurrent()
# ... execute code ...
expr print("Time: \(CFAbsoluteTimeGetCurrent() - start)")
```

### Memory Debugging

Enable in scheme:
1. Edit Scheme > Run > Diagnostics
2. Check "Malloc Stack Logging"
3. Check "Zombie Objects" (for EXC_BAD_ACCESS)

## Common Debugging Scenarios

### Scenario: View Not Appearing

```lldb
# Check if view is in hierarchy
po self.view.superview

# Check frame
po self.view.frame

# Check if hidden
po self.view.isHidden
po self.view.alpha

# Check constraints
po self.view.constraints
```

### Scenario: Button Not Responding

```lldb
# Check if user interaction enabled
po button.isUserInteractionEnabled

# Check if enabled
po button.isEnabled

# Check gesture recognizers
po button.gestureRecognizers

# Check if obscured
po self.view.hitTest(touchPoint, with: nil)
```

### Scenario: Data Not Loading

```lldb
# Check network reachability
po NetworkMonitor.shared.isConnected

# Check API response
po lastResponse?.statusCode
po String(data: lastResponseData, encoding: .utf8)

# Check decoding
expr try JSONDecoder().decode(MyModel.self, from: data)
```

## Useful Xcode Shortcuts

| Action | Shortcut |
|--------|----------|
| Toggle breakpoint | Cmd + \ |
| Step over | F6 |
| Step into | F7 |
| Step out | F8 |
| Continue | Cmd + Ctrl + Y |
| View debugger | Cmd + Shift + D |
| Memory graph | Cmd + Shift + M |
| Debug navigator | Cmd + 7 |

## Debug Console Tips

```lldb
# Clear console
Cmd + K

# Search console output
Cmd + F

# Copy console selection
Cmd + C

# Increase console font
Cmd + + (plus)
```

## Verification Checklist

After debugging session:
- [ ] Remove debug print statements
- [ ] Disable unnecessary breakpoints
- [ ] Turn off diagnostic options in scheme (for production)
- [ ] Remove any `expr` modifications made during debugging

```

swift-diagnostics | SkillHub