5.0 KiB
5.0 KiB
Workflow: Optimize App Performance
<required_reading> Read these reference files NOW:
- references/cli-observability.md
- references/concurrency-patterns.md
- references/swiftui-patterns.md </required_reading>
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>