# Workflow: Debug an Existing macOS App
**Read these reference files NOW:**
1. references/cli-observability.md
2. references/testing-debugging.md
Debugging is iterative. Use whatever gets you to the root cause fastest:
- Small app, obvious symptom → read relevant code
- Large codebase, unclear cause → use tools to narrow down
- Code looks correct but fails → tools reveal runtime behavior
- After fixing → tools verify the fix
The goal is root cause, not following a ritual.
## Step 1: Understand the Symptom
Ask the user or observe:
- What's the actual behavior vs expected?
- When does it happen? (startup, after action, under load)
- Is it reproducible?
- Any error messages?
## Step 2: Build and Check for Compile Errors
```bash
cd /path/to/app
xcodebuild -project AppName.xcodeproj -scheme AppName -derivedDataPath ./build build 2>&1 | xcsift
```
Fix any compile errors first. They're the easiest wins.
## Step 3: Choose Your Approach
**If you know roughly where the problem is:**
→ Read that code, form hypothesis, test it
**If you have no idea where to start:**
→ Use tools to narrow down (Step 4)
**If code looks correct but behavior is wrong:**
→ Runtime observation (Step 4) reveals what's actually happening
## Step 4: Runtime Diagnostics
Launch with log streaming:
```bash
# Terminal 1: stream logs
log stream --level debug --predicate 'subsystem == "com.company.AppName"'
# Terminal 2: launch
open ./build/Build/Products/Debug/AppName.app
```
**Match symptom to tool:**
| Symptom | Tool | Command |
|---------|------|---------|
| Memory growing / leak suspected | leaks | `leaks AppName` |
| UI freezes / hangs | spindump | `spindump AppName -o /tmp/hang.txt` |
| Crash | crash report | `cat ~/Library/Logs/DiagnosticReports/AppName_*.ips` |
| Slow performance | time profiler | `xcrun xctrace record --template 'Time Profiler' --attach AppName` |
| Race condition suspected | thread sanitizer | Build with `-enableThreadSanitizer YES` |
| Nothing happens / silent failure | logs | Check log stream output |
**Interact with the app** to trigger the issue. Use `cliclick` if available:
```bash
cliclick c:500,300 # click at coordinates
```
## Step 5: Interpret Tool Output
| Tool Shows | Likely Cause | Where to Look |
|------------|--------------|---------------|
| Leaked object: DataService | Retain cycle | Closures capturing self in DataService |
| Main thread blocked in computeX | Sync work on main | That function - needs async |
| Crash at force unwrap | Nil where unexpected | The unwrap site + data flow to it |
| Thread sanitizer warning | Data race | Shared mutable state without sync |
| High CPU in function X | Hot path | That function - algorithm or loop issue |
## Step 6: Read Relevant Code
Now you know where to look. Read that specific code:
- Understand what it's trying to do
- Identify the flaw
- Consider edge cases
## Step 7: Fix the Root Cause
Not the symptom. The actual cause.
**Bad:** Add nil check to prevent crash
**Good:** Fix why the value is nil in the first place
**Bad:** Add try/catch to swallow error
**Good:** Fix what's causing the error
## Step 8: Verify the Fix
Use the same diagnostic that found the issue:
```bash
# Rebuild
xcodebuild -project AppName.xcodeproj -scheme AppName build
# Launch and test
open ./build/Build/Products/Debug/AppName.app
# Run same diagnostic
leaks AppName # should show 0 leaks now
```
## Step 9: Prevent Regression
If the bug was significant, write a test:
```bash
xcodebuild test -project AppName.xcodeproj -scheme AppName
```
## Memory Leaks
**Symptom:** Memory grows over time, `leaks` shows retained objects
**Common causes:**
- Closure captures `self` strongly: `{ self.doThing() }`
- Delegate not weak: `var delegate: SomeProtocol`
- Timer not invalidated
**Fix:** `[weak self]`, `weak var delegate`, `timer.invalidate()`
## UI Freezes
**Symptom:** App hangs, spinning beachball, spindump shows main thread blocked
**Common causes:**
- Sync network call on main thread
- Heavy computation on main thread
- Deadlock from incorrect async/await usage
**Fix:** `Task { }`, `Task.detached { }`, check actor isolation
## Crashes
**Symptom:** App terminates, crash report generated
**Common causes:**
- Force unwrap of nil: `value!`
- Array index out of bounds
- Unhandled error
**Fix:** `guard let`, bounds checking, proper error handling
## Silent Failures
**Symptom:** Nothing happens, no error, no crash
**Common causes:**
- Error silently caught and ignored
- Async task never awaited
- Condition always false
**Fix:** Add logging, check control flow, verify async chains
## Performance Issues
**Symptom:** Slow, high CPU, laggy UI
**Common causes:**
- O(n²) or worse algorithm
- Unnecessary re-renders in SwiftUI
- Repeated expensive operations
**Fix:** Better algorithm, memoization, `let _ = Self._printChanges()`
```bash
# Build errors (structured JSON)
xcodebuild build 2>&1 | xcsift
# Real-time logs
log stream --level debug --predicate 'subsystem == "com.company.App"'
# Memory leaks
leaks AppName
# UI hangs
spindump AppName -o /tmp/hang.txt
# Crash reports
cat ~/Library/Logs/DiagnosticReports/AppName_*.ips | head -100
# Memory regions
vmmap --summary AppName
# Heap analysis
heap AppName
# Attach debugger
lldb -n AppName
# CPU profiling
xcrun xctrace record --template 'Time Profiler' --attach AppName
# Thread issues (build flag)
xcodebuild build -enableThreadSanitizer YES
```