Files
gh-glittercowboy-taches-cc-…/skills/expertise/macos-apps/workflows/optimize-performance.md
2025-11-29 18:28:37 +08:00

5.0 KiB

Workflow: Optimize App Performance

<required_reading> Read these reference files NOW:

  1. references/cli-observability.md
  2. references/concurrency-patterns.md
  3. references/swiftui-patterns.md </required_reading>
Measure first, optimize second. Never optimize based on assumptions. Profile → Identify bottleneck → Fix → Measure again → Repeat ## Step 1: Define the Problem

Ask the user:

  • What feels slow? (startup, specific action, scrolling, etc.)
  • How slow? (seconds, milliseconds, "laggy")
  • When did it start? (always, after recent change, with more data)

Step 2: Measure Current Performance

CPU Profiling:

# Record 30 seconds of activity
xcrun xctrace record \
  --template 'Time Profiler' \
  --time-limit 30s \
  --output profile.trace \
  --launch -- ./build/Build/Products/Debug/AppName.app/Contents/MacOS/AppName

Memory:

# While app is running
vmmap --summary AppName
heap AppName
leaks AppName

Startup time:

# Measure launch to first frame
time open -W ./build/Build/Products/Debug/AppName.app

Step 3: Identify Bottlenecks

From Time Profiler:

  • Look for functions with high "self time"
  • Check main thread for blocking operations
  • Look for repeated calls that could be cached

From memory tools:

  • Large allocations that could be lazy-loaded
  • Objects retained longer than needed
  • Duplicate data in memory

SwiftUI re-renders:

// Add to any view to see why it re-renders
var body: some View {
    let _ = Self._printChanges()
    // ...
}

Step 4: Common Optimizations

Main Thread

Problem: Heavy work on main thread

// Bad
func loadData() {
    let data = expensiveComputation() // blocks UI
    self.items = data
}

// Good
func loadData() async {
    let data = await Task.detached {
        expensiveComputation()
    }.value
    await MainActor.run {
        self.items = data
    }
}

SwiftUI

Problem: Unnecessary re-renders

// Bad - entire view rebuilds when any state changes
struct ListView: View {
    @State var items: [Item]
    @State var searchText: String
    // ...
}

// Good - extract subviews with their own state
struct ListView: View {
    var body: some View {
        VStack {
            SearchBar() // has its own @State
            ItemList()  // only rebuilds when items change
        }
    }
}

Problem: Expensive computation in body

// Bad
var body: some View {
    List(items.sorted().filtered()) // runs every render

// Good
var sortedItems: [Item] { // or use .task modifier
    items.sorted().filtered()
}
var body: some View {
    List(sortedItems)
}

Data Loading

Problem: Loading all data upfront

// Bad
init() {
    self.allItems = loadEverything() // slow startup
}

// Good - lazy loading
func loadItemsIfNeeded() async {
    guard items.isEmpty else { return }
    items = await loadItems()
}

Problem: No caching

// Bad
func getImage(for url: URL) async -> NSImage {
    return await downloadImage(url) // downloads every time
}

// Good
private var imageCache: [URL: NSImage] = [:]
func getImage(for url: URL) async -> NSImage {
    if let cached = imageCache[url] { return cached }
    let image = await downloadImage(url)
    imageCache[url] = image
    return image
}

Collections

Problem: O(n²) operations

// Bad - O(n) lookup in array
items.first { $0.id == targetId }

// Good - O(1) lookup with dictionary
itemsById[targetId]

Problem: Repeated filtering

// Bad
let activeItems = items.filter { $0.isActive } // called repeatedly

// Good - compute once, update when needed
@Published var activeItems: [Item] = []
func updateActiveItems() {
    activeItems = items.filter { $0.isActive }
}

Step 5: Measure Again

After each optimization:

# Re-run profiler
xcrun xctrace record --template 'Time Profiler' ...

# Compare metrics

Did it actually improve? If not, revert and try different approach.

Step 6: Prevent Regression

Add performance tests:

func testStartupPerformance() {
    measure {
        // startup code
    }
}

func testScrollingPerformance() {
    measure(metrics: [XCTCPUMetric(), XCTMemoryMetric()]) {
        // scroll simulation
    }
}

<performance_targets>

Metric Target Unacceptable
App launch < 1 second > 3 seconds
Button response < 100ms > 500ms
List scrolling 60 fps < 30 fps
Memory (idle) < 100MB > 500MB
Memory growth Stable Unbounded
</performance_targets>

<tools_reference>

# CPU profiling
xcrun xctrace record --template 'Time Profiler' --attach AppName

# Memory snapshot
vmmap --summary AppName
heap AppName

# Allocations over time
xcrun xctrace record --template 'Allocations' --attach AppName

# Energy impact
xcrun xctrace record --template 'Energy Log' --attach AppName

# System trace (comprehensive)
xcrun xctrace record --template 'System Trace' --attach AppName

</tools_reference>