
Swiftui Performance
Diagnose SwiftUI hangs, hitches, and over-invalidation using the WWDC23 measure–identify–optimize–re-measure loop.
Overview
SwiftUI Performance is an agent skill most often used in Build (also Ship) that applies the WWDC23 SwiftUI performance mental model to fix hangs, hitches, and excessive updates.
Install
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swiftui-performanceWhat is this skill?
- WWDC23 mental model: only required work per frame; triage too much work, too-frequent updates, or unstable identity
- Four-step loop: Measure, Identify, Optimize, Re-measure—explicitly do not skip re-measure
- Dependency precision for @State, @Binding, @Observable, @Environment, and container identity (ForEach, List, Table)
- Identity rules for lists and tables plus initialization and lifecycle pitfalls
- Debugging tools and fix patterns with a post-change verification checklist
- Four-step performance loop: Measure, Identify, Optimize, Re-measure
Adoption & trust: 2k installs on skills.sh; 713 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your SwiftUI screen stutters or freezes and you cannot tell whether the culprit is invalidation breadth, heavy body work, or broken list identity.
Who is it for?
Indie iOS developers shipping SwiftUI apps who want WWDC-aligned triage instead of random view refactors.
Skip if: UIKit-only codebases or teams that will not use Instruments and structured re-measure after changes.
When should I use this skill?
SwiftUI screens show hangs, hitches, or excessive updates and you need WWDC23-style dependency, identity, and profiling guidance.
What do I get? / Deliverables
You follow a measured optimization loop with precise dependencies and stable identity, then re-measure to confirm frame work actually dropped.
- Identified invalidation or identity root cause
- Applied fix patterns with post-change verification list
Recommended Skills
Journey fit
Spans multiple journey phases - primary shelf plus alternate fits below.
SwiftUI performance work happens while you are building the UI—the canonical shelf is Build → frontend for iOS screens. Mobile SwiftUI views, lists, and observable state are frontend concerns even when fixes touch architecture.
Where it fits
Narrow @Observable dependencies so only the affected row redraws in a long List.
Profile a pre-release build and re-measure after fixing unstable ForEach IDs.
Reproduce a reported hang and verify initialization and lifecycle pitfalls from the checklist.
How it compares
Framework-specific performance playbook—use instead of generic React-style memo advice that does not map to SwiftUI invalidation.
Common Questions / FAQ
Who is swiftui-performance for?
Solo and small-team SwiftUI developers who need a structured WWDC23-based approach to scroll hitches, hangs, and update storms.
When should I use swiftui-performance?
Use it in Build while implementing lists and observable state; in Ship under perf when profiling before App Store submission; and in Operate when user reports tie to UI jank you need to reproduce and fix.
Is swiftui-performance safe to install?
See the Security Audits panel on this Prism page; the skill is documentation and procedure only and does not by itself execute shell or network actions.
SKILL.md
READMESKILL.md - Swiftui Performance
# Demystify SwiftUI Performance (WWDC23) (Summary) Context: WWDC23 session on building a mental model for SwiftUI performance and triaging hangs, hitches, and excessive update work. ## Contents - Core mental model - Dependencies and invalidation - Expensive body work - Identity rules for lists and tables - Initialization and lifecycle pitfalls - Debugging tools - Fix patterns - What to verify after a change ## Core mental model SwiftUI performance starts with one rule: only work that is required for the current state should happen for the current frame. A slow screen usually means one of these is false: - too much work happens per update - updates happen too often - identity is unstable, so SwiftUI redoes work it could have reused The session's practical loop is: - Measure - Identify - Optimize - Re-measure Do not skip the last step. SwiftUI optimizations are easy to misjudge by eye. ## Dependencies and invalidation A view updates when one of its dependencies changes. Common dependency sources: - `@State` - `@Binding` - `@Observable` / `@ObservedObject` - `@Environment` - container-derived identity (`ForEach`, `List`, `Table`) The performance goal is not "fewer dependencies" in the abstract. The goal is **precise dependencies** so only the view that needs to update actually updates. ### Practical implications - Avoid a row depending on a whole collection if it only needs one element. - Avoid broad environment-driven updates for fast-changing values. - Extract subviews when a smaller view can read a smaller state surface. ### Debug-only dependency inspection `Self._printChanges()` is useful in debug builds when you are not sure why a view keeps updating. Use it to answer: - which property changed? - which parent view re-rendered? - is the view reacting to state it should not care about? Do not treat `_printChanges()` output as a shipping-time profiling tool. ## Expensive body work View bodies need to stay cheap. Typical mistakes: - string formatting in `body` - array filtering and sorting in `body` - expensive image work in `body` - constructing large attributed strings during render - initializing heavy models in-line with view creation ```swift // DON'T var body: some View { List(items.filter(shouldShow).sorted(by: sortRule)) { item in Text(numberFormatter.string(from: item.value as NSNumber) ?? "") } } // DO var body: some View { List(viewModel.visibleItems) { item in Text(item.formattedValue) } } ``` The winning pattern is precomputation at the model boundary, not clever work in `body`. ## Identity rules for lists and tables Identity is one of the biggest hidden performance levers in SwiftUI. ### Stable identity matters Use stable IDs that survive refreshes and sorting. If identity churns, SwiftUI cannot reuse rows, preserve animations, or diff efficiently. ### Constant row count matters Inside `ForEach`, SwiftUI expects a predictable mapping between data elements and rendered views. Avoid patterns like: ```swift ForEach(items) { item in if item.isVisible { Row(item: item) } } ``` Prefer: ```swift ForEach(visibleItems) { item in Row(item: item) } ``` ### Avoid `AnyView` in hot list rows Type erasure can hide useful structural information and increase work in large lists or tables. ### Table-specific note `TableRow` resolves to a single row. Keep row structure predictable and use the streamlined `Table` APIs when possible. ## Initialization and lifecycle pitfalls ### Heavy model creation in view init/body Keep view initialization lightweight. Start async work with `.task` or from a model object. ```swift // DON'T struct DetailView: View { let loader = BigLoader() // heavy construction } // DO struct DetailView: View { @State private var model: DetailModel? var body: some View { content .task { model = await loadDetailModel() } } } ``` ### Hidden work from compute