
Swiftui Performance Audit
- 144 installs
- 6.5k repo stars
- Updated August 3, 2026
- steipete/agent-scripts
Use swiftui-performance-audit for development tasks
About
swiftui-performance-audit: A skill for development. This provides functionality for development workflows.
- swiftui-performance-audit
Swiftui Performance Audit by the numbers
- 144 all-time installs (skills.sh)
- +4 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #2,545 of 4,348 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/steipete/agent-scripts --skill swiftui-performance-auditAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 144 |
|---|---|
| repo stars | ★ 6.5k |
| Last updated | August 3, 2026 |
| Repository | steipete/agent-scripts ↗ |
What it does
Use swiftui-performance-audit for development tasks
Files
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 (
idchurn,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 (
idchurn,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/@Observablecloser to leaf views). - Stabilize identities for
ForEachand 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
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:
final class DistanceFormatter {
static let shared = DistanceFormatter()
let number = NumberFormatter()
let measure = MeasurementFormatter()
}Computed properties that do heavy work
var filtered: [Item] {
items.filter { $0.isEnabled } // runs on every body eval
}Prefer precompute or cache on change:
@State private var filtered: [Item] = []
// update filtered when inputs changeSorting/filtering in body or ForEach
List {
ForEach(items.sorted(by: sortRule)) { item in
Row(item)
}
}Prefer sort once before view updates:
let sortedItems = items.sorted(by: sortRule)Inline filtering in ForEach
ForEach(items.filter { $0.isEnabled }) { item in
Row(item)
}Prefer a prefiltered collection with stable identity.
Unstable identity
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
Image(uiImage: UIImage(data: data)!)Prefer decode/downsample off the main thread and store the result.
Broad dependencies in observable models
@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
Demystify SwiftUI Performance (WWDC23) (Summary)
Context: WWDC23 session on building a mental model for SwiftUI performance and triaging hangs/hitches.
Performance loop
- Measure -> Identify -> Optimize -> Re-measure.
- Focus on concrete symptoms (slow navigation, broken animations, spinning cursor).
Dependencies and updates
- Views form a dependency graph; dynamic properties are a frequent source of updates.
- Use
Self._printChanges()in debug only to inspect extra dependencies. - Eliminate unnecessary dependencies by extracting views or narrowing state.
- Consider
@Observablefor more granular property tracking.
Common causes of slow updates
- Expensive view bodies (string interpolation, filtering, formatting).
- Dynamic property instantiation and state initialization in
body. - Slow identity resolution in lists/tables.
- Hidden work: bundle lookups, heap allocations, repeated string construction.
Avoid slow initialization in view bodies
- Don’t create heavy models synchronously in view bodies.
- Use
.taskto fetch async data and keepinitlightweight.
Lists and tables identity rules
- Stable identity is critical for performance and animation.
- Ensure a constant number of views per element in
ForEach. - Avoid inline filtering in
ForEach; pre-filter and cache collections. - Avoid
AnyViewin list rows; it hides identity and increases cost. - Flatten nested
ForEachwhen possible to reduce overhead.
Table specifics
TableRowresolves to a single row; row count must be constant.- Prefer the streamlined
Tableinitializer to enforce constant rows. - Use explicit IDs for back deployment when needed.
Debugging aids
- Use Instruments for hangs and hitches.
- Use
_printChangesto validate dependency assumptions during debug.
Optimizing SwiftUI Performance with Instruments (Summary)
Context: WWDC session introducing the next-generation SwiftUI Instrument in Instruments 26 and how to diagnose SwiftUI-specific bottlenecks.
Key takeaways
- Profile SwiftUI issues with the SwiftUI template (SwiftUI instrument + Time Profiler + Hangs/Hitches).
- Long view body updates are a common bottleneck; use "Long View Body Updates" to identify slow bodies.
- Set inspection range on a long update and correlate with Time Profiler to find expensive frames.
- Keep work out of
body: move formatting, sorting, image decoding, and other expensive work into cached or precomputed paths. - Use Cause & Effect Graph to diagnose why updates occur; SwiftUI is declarative, so backtraces are often unhelpful.
- Avoid broad dependencies that trigger many updates (e.g.,
@Observablearrays or global environment reads). - Prefer granular view models and scoped state so only the affected view updates.
- Environment values update checks still cost time; avoid placing fast-changing values (timers, geometry) in environment.
- Profile early and often during feature development to catch regressions.
Suggested workflow (condensed)
1. Record a trace in Release mode using the SwiftUI template. 2. Inspect "Long View Body Updates" and "Other Long Updates." 3. Zoom into a long update, then inspect Time Profiler for hot frames. 4. Fix slow body work by moving heavy logic into precomputed/cache paths. 5. Use Cause & Effect Graph to identify unintended update fan-out. 6. Re-record and compare the update counts and hitch frequency.
Example patterns from the session
- Caching formatted distance strings in a location manager instead of computing in
body. - Replacing a dependency on a global favorites array with per-item view models to reduce update fan-out.
Understanding Hangs in Your App (Summary)
Context: Apple guidance on identifying hangs caused by long-running main-thread work and understanding the main run loop.
Key concepts
- A hang is a noticeable delay in a discrete interaction (typically >100 ms).
- Hangs almost always come from long-running work on the main thread.
- The main run loop processes UI events, timers, and main-queue work sequentially.
Main-thread work stages
- Event delivery to the correct view/handler.
- Your code: state updates, data fetch, UI changes.
- Core Animation commit to the render server.
Why the main run loop matters
- Only the main thread can update UI safely.
- The run loop is the foundation that executes main-queue work.
- If the run loop is busy, it can’t handle new events; this causes hangs.
Diagnosing hangs
- Observe the main run loop’s busy periods: healthy loops sleep most of the time.
- Hang detection typically flags busy periods >250 ms.
- The Hangs instrument can be configured to lower thresholds.
Practical takeaways
- Keep main-thread work short; offload heavy work from event handlers.
- Avoid long-running tasks on the main dispatch queue or main actor.
- Use run loop behavior as a proxy for user-perceived responsiveness.
Understanding and Improving SwiftUI Performance (Summary)
Context: Apple guidance on diagnosing SwiftUI performance with Instruments and applying design patterns to reduce long or frequent updates.
Core concepts
- SwiftUI is declarative; view updates are driven by state, environment, and observable data dependencies.
- View bodies must compute quickly to meet frame deadlines; slow or frequent updates lead to hitches.
- Instruments is the primary tool to find long-running updates and excessive update frequency.
Instruments workflow
1. Profile via Product > Profile. 2. Choose the SwiftUI template and record. 3. Exercise the target interaction. 4. Stop recording and inspect the SwiftUI track + Time Profiler.
SwiftUI timeline lanes
- Update Groups: overview of time SwiftUI spends calculating updates.
- Long View Body Updates: orange >500us, red >1000us.
- Long Platform View Updates: AppKit/UIKit hosting in SwiftUI.
- Other Long Updates: geometry/text/layout and other SwiftUI work.
- Hitches: frame misses where UI wasn’t ready in time.
Diagnose long view body updates
- Expand the SwiftUI track; inspect module-specific subtracks.
- Set Inspection Range and correlate with Time Profiler.
- Use call tree or flame graph to identify expensive frames.
- Repeat the update to gather enough samples for analysis.
- Filter to a specific update (Show Calls Made by
MySwiftUIView.body).
Diagnose frequent updates
- Use Update Groups to find long active groups without long updates.
- Set inspection range on the group and analyze update counts.
- Use Cause graph ("Show Causes") to see what triggers updates.
- Compare causes with expected data flow; prioritize the highest-frequency causes.
Remediation patterns
- Move expensive work out of
bodyand cache results. - Use
Observable()macro to scope dependencies to properties actually read. - Avoid broad dependencies that fan out updates to many views.
- Reduce layout churn; isolate state-dependent subtrees from layout readers.
- Avoid storing closures that capture parent state; precompute child views.
- Gate frequent updates (e.g., geometry changes) by thresholds.
Verification
- Re-record after changes to confirm reduced update counts and fewer hitches.