ios-performance-profiler
Skill4 starsChanged 3 months agoMITView on GitHub
Identifies potential performance bottlenecks in SwiftUI code — expensive view body computation, unnecessary redraws, identity instability, concurrency issues, and memory problems. Use for lists, animations, complex layouts, remote data, or when the user asks about performance.
In this repository20 other files it gives its agents.
- AGENTS.mdAGENTS.md
- accessibility-annotationsSkill
- android-accessibility-validatorSkill
- android-compose-architecture-reviewSkill
- android-kotlin-api-design-reviewerSkill
- android-performance-profilerSkill
- flutter-accessibility-validatorSkill
- flutter-bug-investigationSkill
- flutter-code-reviewSkill
- flutter-design-architectureSkill
- flutter-feature-analysisSkill
- flutter-feature-implementationSkill
- flutter-performance-optimizerSkill
- ios-accessibility-validatorSkill
- ios-swift-api-design-reviewerSkill
- ios-swiftui-architecture-reviewSkill
- shared-bug-investigationSkill
- shared-perf-comparison-videoSkill
- shared-product-motion-videoSkill
- shared-task-workflowSkill
The file
Files
Performance Profiler
View Identity & Diffing
- Stable, persistent identity (
Identifiablewith stableid, avoid random UUIDs in rows) - Prefer value types + structural identity over class reference identity when possible
- Avoid
.id()unless absolutely necessary (use stable keys instead)
Body Computation & Redraws
- No heavy work (network, decoding, sorting, filtering) inside
body - Expensive derived state computed once and stored (
.onAppear, ViewModel, or memoized computed property) -
@State,@Bindable,@Observableproperties are narrowly scoped - Child views extracted when parent state changes frequently
- Prefer
@Observable(Observation framework) over@ObservableObject/@Publishedfor new code — finer-grained invalidation, less boilerplate
Lists & Large Collections
- Use
List/LazyVStack/LazyHGridinstead ofScrollView + ForEach + VStack - Rows are lightweight; avoid
@StateObject/@ObservedObjectinside row bodies - Use stable
Identifiableconformance or explicitid:key path
Concurrency & Main Actor (Swift 6 era)
-
@MainActorused appropriately on view-bound types (avoid overuse → thread hops) - Nonisolated properties / computed vars when safe
- Actors used for model / service layers; avoid
@MainActoron pure data models - No data races when using
@Observabletypes across concurrency domains - Prefer
.taskover.onAppearfor async work (automatic cancellation on view disappear, structured concurrency)
Environment & State Propagation
- Avoid injecting large
@Observableobjects via@Environmentwhen only a small slice of state is needed — extract a child view that reads only what it needs - Minimize over-subscription from
EnvironmentObject— split into focused, smaller observable types if necessary
Memory & Retain Cycles
-
[weak self]in async / escaping closures that captureself -
@StateObjectfor view-owned view models (not inListrows) - Remote images use proper caching (
AsyncImagewith cache, orKingfisher/Nuke) - Downscale images to display size before rendering (
.resizable()+.frame()alone doesn't reduce memory) - Use
preparingThumbnail(of:)for large images - Avoid long-lived strong references in
@Observable/@ObservableObject
Layout & Geometry
- Minimize
GeometryReaderusage inside scroll views (causes layout thrashing) - Avoid
PreferenceKeyupdates that trigger parent re-layouts in tight loops - Use
drawingGroup()for complex vector paths / heavy Canvas content
Animations & Transitions
- Scope animations narrowly (
.animation(…, value: …)) instead of global.animation() - Use
.matchedGeometryEffectonly when necessary (expensive) - Test with reduced motion enabled
Navigation & Sheets
- Use lazy destination resolution in
NavigationStack(avoid pre-building destination views) - Ensure
.sheet/.fullScreenCovercontent isn't computed until presentation - Avoid deep view hierarchies in destinations — flatten or split into smaller views
Quick Wins
| Issue | Fix / Pattern | Severity |
| Parent redraws child unnecessarily | Extract stable child view / use Equatable conformance | 🟡 |
Heavy logic in body | Move to ViewModel / .task / computed property | 🔴 |
Unstable ForEach IDs | Use stable id: \.self or persistent model ID | 🔴 |
| Retain cycle | \[weak self\] in closures | 🔴 |
@StateObject recreated in List row | Move ownership to parent / use @Observable value type | 🔴 |
| Broad / unnecessary animations | .animation(nil) or value-specific .animation(…, value:) | 🟡 |
| Frequent main-actor hops | Use nonisolated properties, actors for model layer | 🟡 |
GeometryReader in scroll views | Move geometry reads outside scroll or cache values | 🟡 |
| Large images not downscaled | preparingThumbnail(of:) or resize before display | 🟡 |
Over-subscribed @Environment | Extract child view reading only needed state | 🟡 |
| Eager sheet / destination init | Lazy destination resolution in NavigationStack | 🟡 |
Still using @ObservableObject | Migrate to @Observable (Observation framework) | 🟢 |
| Complex vector / Canvas paths | Use drawingGroup() for off-screen rendering | 🟢 |
Debug Helpers (Xcode 16+)
// In any View body — logs when/why view redraws
let _ = Self._printChanges()// os_signpost for custom performance intervals
import os.signpost
let log = OSLog(subsystem: "com.app.performance", category: "ViewLoading")
let signpostID = OSSignpostID(log: log)
os_signpost(.begin, log: log, name: "LoadData", signpostID: signpostID)
// ... work ...
os_signpost(.end, log: log, name: "LoadData", signpostID: signpostID)Instruments & Tools
- SwiftUI template + Animation instrument in Instruments
- SwiftUI View Body instrument (Xcode 16+) — tracks body evaluations per view
- SwiftUI Performance HUD —
⌥⌘Pin Simulator - MetricKit /
MXMetricManager— production performance & diagnostic data - Memory Graph Debugger — catch retain cycles and leaks at runtime
Severity Legend
🔴 Critical — Visible lag, dropped frames, memory growth, crashes
🟡 Moderate — Noticeable jank on low-end devices or large datasets
🟢 Minor — Optimization opportunity / future-proofs for larger scale
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

