# Workflow: Optimize App Performance **Read these reference files NOW:** 1. references/cli-observability.md 2. references/concurrency-patterns.md 3. references/swiftui-patterns.md 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:** ```bash # 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:** ```bash # While app is running vmmap --summary AppName heap AppName leaks AppName ``` **Startup time:** ```bash # 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:** ```swift // 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 ```swift // 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 ```swift // 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 ```swift // 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 ```swift // Bad init() { self.allItems = loadEverything() // slow startup } // Good - lazy loading func loadItemsIfNeeded() async { guard items.isEmpty else { return } items = await loadItems() } ``` **Problem:** No caching ```swift // 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 ```swift // Bad - O(n) lookup in array items.first { $0.id == targetId } // Good - O(1) lookup with dictionary itemsById[targetId] ``` **Problem:** Repeated filtering ```swift // 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: ```bash # 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: ```swift func testStartupPerformance() { measure { // startup code } } func testScrollingPerformance() { measure(metrics: [XCTCPUMetric(), XCTMemoryMetric()]) { // scroll simulation } } ``` | 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 | ```bash # 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 ```