home / skills / steipete / agent-scripts / swiftui-performance-audit

swiftui-performance-audit skill

/skills/swiftui-performance-audit

This skill audits SwiftUI performance from code review to instrumentation guidance, diagnosing slow rendering and guiding targeted remediations.

npx playbooks add skill steipete/agent-scripts --skill swiftui-performance-audit

Review the files below or copy the command above to add this skill to your agents.

Files (5)
SKILL.md
5.4 KB
---
name: swiftui-performance-audit
description: Audit and improve SwiftUI runtime performance from code review and architecture. Use for requests to diagnose slow rendering, janky scrolling, high CPU/memory usage, excessive view updates, or layout thrash in SwiftUI apps, and to provide guidance for user-run Instruments profiling when code review alone is insufficient.
---

# SwiftUI Performance Audit

_Attribution: copied from @Dimillian’s `Dimillian/Skills` (2025-12-31)._

## Overview

Audit SwiftUI view performance end-to-end, from instrumentation and baselining to root-cause analysis and concrete remediation steps.

## Workflow Decision Tree

- If the user provides code, start with "Code-First Review."
- If the user only describes symptoms, ask for minimal code/context, then do "Code-First Review."
- If code review is inconclusive, go to "Guide the User to Profile" and ask for a trace or screenshots.

## 1. Code-First Review

Collect:
- Target view/feature code.
- Data flow: state, environment, observable models.
- Symptoms and reproduction steps.

Focus on:
- View invalidation storms from broad state changes.
- Unstable identity in lists (`id` churn, `UUID()` per render).
- Heavy work in `body` (formatting, sorting, image decoding).
- Layout thrash (deep stacks, `GeometryReader`, preference chains).
- Large images without downsampling or resizing.
- Over-animated hierarchies (implicit animations on large trees).

Provide:
- Likely root causes with code references.
- Suggested fixes and refactors.
- If needed, a minimal repro or instrumentation suggestion.

## 2. Guide the User to Profile

Explain how to collect data with Instruments:
- Use the SwiftUI template in Instruments (Release build).
- Reproduce the exact interaction (scroll, navigation, animation).
- Capture SwiftUI timeline and Time Profiler.
- Export or screenshot the relevant lanes and the call tree.

Ask for:
- Trace export or screenshots of SwiftUI lanes + Time Profiler call tree.
- Device/OS/build configuration.

## 3. Analyze and Diagnose

Prioritize likely SwiftUI culprits:
- View invalidation storms from broad state changes.
- Unstable identity in lists (`id` churn, `UUID()` per render).
- Heavy work in `body` (formatting, sorting, image decoding).
- Layout thrash (deep stacks, `GeometryReader`, preference chains).
- Large images without downsampling or resizing.
- Over-animated hierarchies (implicit animations on large trees).

Summarize findings with evidence from traces/logs.

## 4. Remediate

Apply targeted fixes:
- Narrow state scope (`@State`/`@Observable` closer to leaf views).
- Stabilize identities for `ForEach` and lists.
- Move heavy work out of `body` (precompute, cache, `@State`).
- Use `equatable()` or value wrappers for expensive subtrees.
- Downsample images before rendering.
- Reduce layout complexity or use fixed sizing where possible.

## Common Code Smells (and Fixes)

Look for these patterns during code review.

### Expensive formatters in `body`

```swift
var body: some View {
    let number = NumberFormatter() // slow allocation
    let measure = MeasurementFormatter() // slow allocation
    Text(measure.string(from: .init(value: meters, unit: .meters)))
}
```

Prefer cached formatters in a model or a dedicated helper:

```swift
final class DistanceFormatter {
    static let shared = DistanceFormatter()
    let number = NumberFormatter()
    let measure = MeasurementFormatter()
}
```

### Computed properties that do heavy work

```swift
var filtered: [Item] {
    items.filter { $0.isEnabled } // runs on every body eval
}
```

Prefer precompute or cache on change:

```swift
@State private var filtered: [Item] = []
// update filtered when inputs change
```

### Sorting/filtering in `body` or `ForEach`

```swift
List {
    ForEach(items.sorted(by: sortRule)) { item in
        Row(item)
    }
}
```

Prefer sort once before view updates:

```swift
let sortedItems = items.sorted(by: sortRule)
```

### Inline filtering in `ForEach`

```swift
ForEach(items.filter { $0.isEnabled }) { item in
    Row(item)
}
```

Prefer a prefiltered collection with stable identity.

### Unstable identity

```swift
ForEach(items, id: \.self) { item in
    Row(item)
}
```

Avoid `id: \.self` for non-stable values; use a stable ID.

### Image decoding on the main thread

```swift
Image(uiImage: UIImage(data: data)!)
```

Prefer decode/downsample off the main thread and store the result.

### Broad dependencies in observable models

```swift
@Observable class Model {
    var items: [Item] = []
}

var body: some View {
    Row(isFavorite: model.items.contains(item))
}
```

Prefer granular view models or per-item state to reduce update fan-out.

## 5. Verify

Ask the user to re-run the same capture and compare with baseline metrics.
Summarize the delta (CPU, frame drops, memory peak) if provided.

## Outputs

Provide:
- A short metrics table (before/after if available).
- Top issues (ordered by impact).
- Proposed fixes with estimated effort.

## References

Add Apple documentation and WWDC resources under `references/` as they are supplied by the user.
- Optimizing SwiftUI performance with Instruments: `references/optimizing-swiftui-performance-instruments.md`
- Understanding and improving SwiftUI performance: `references/understanding-improving-swiftui-performance.md`
- Understanding hangs in your app: `references/understanding-hangs-in-your-app.md`
- Demystify SwiftUI performance (WWDC23): `references/demystify-swiftui-performance-wwdc23.md`

Overview

This skill audits and improves SwiftUI runtime performance through code review, architectural guidance, and profiling workflows. It diagnoses slow rendering, janky scrolling, excessive view updates, layout thrash, and high CPU or memory usage, then recommends concrete fixes and verification steps. Use it to get prioritized issues, remediation plans, and guidance for user-run Instruments captures when code review is inconclusive.

How this skill works

Start with a code-first review when code is provided: inspect view bodies, state flow, list identity, image handling, and layout complexity for common performance smells. If code alone is insufficient, guide the user to collect Instruments traces (SwiftUI timeline + Time Profiler) and analyze lanes and call trees. Produce a prioritized findings list, suggested refactors, and verification steps including before/after metrics and instrumentation advice.

When to use it

  • You observe janky scrolling or missed frames in a SwiftUI view.
  • High CPU or memory spikes tied to UI interactions or navigation.
  • Excessive view updates or large re-render counts during simple state changes.
  • Suspected layout thrash from GeometryReader, preference chains, or deep stacks.
  • You need concrete steps to profile with Instruments and interpret traces.

Best practices

  • Scope state narrowly: push @State/@Observable closer to the leaf views to reduce invalidation fan-out.
  • Stabilize identities in lists: avoid UUID() or id: \.self for non-stable values.
  • Move heavy work out of body: precompute, cache, or perform formatting/decoding off the main thread.
  • Downsample and decode large images off the main thread and cache results for reuse.
  • Prefer fixed sizing or simplified layout for complex hierarchies; avoid excessive GeometryReader/preference chains.
  • Use equatable() or lightweight wrappers for expensive subtrees to short-circuit updates.

Example use cases

  • Review a slow list view: identify id churn, heavy per-row work in body, and propose caching or view-model split.
  • Diagnose janky animation across many rows: find implicit animations on large trees and suggest targeted animations or reduced scope.
  • Guide a developer to capture SwiftUI timeline and Time Profiler and interpret hotspots to show where work is happening.
  • Recommend architecture changes to separate per-item observable state and reduce update fan-out in a feed-style app.

FAQ

What do you need to start a code-first review?

Provide the target view code, related state/observable models, reproduction steps, and a short description of the symptom and device/build configuration.

When should I capture Instruments traces?

Capture traces when code review doesn’t reveal a clear culprit or when real-time behavior differs from static inspection. Use a Release build, reproduce the interaction, and include SwiftUI timeline plus Time Profiler lanes.