This repository uses Apple performance guidance as an engineering standard, not as optional cleanup work.
Every feature change should preserve:
- responsive discrete interactions
- smooth continuous interactions
- narrow SwiftUI update scopes
- measurable before/after verification
This guide is the standing rulebook for new work in this repo.
Core Apple references this guide is based on:
- Improving Your App's Performance
- Understanding and improving SwiftUI performance
- Performance analysis
- Improving app responsiveness
- Understanding user interface responsiveness
- Understanding hitches in your app
- Diagnosing performance issues early
- Improving your app's rendering efficiency
- Reducing your app's launch time
- Reducing disk writes
- Reducing terminations in your app
- MetricKit
- Logging
- XCTest
- Keep synchronous main-thread work for discrete interactions well under
100 ms. - Treat continuous interactions like drag, scroll, resize, and timeline movement as frame-budget work.
- Prefer staying below
5 msof app-side main-thread work during continuous interactions where possible.
- Keep view bodies cheap.
- Do not perform heavy filtering, sorting, decoding, formatting, or aggregation directly in
body-adjacent computed properties unless the result is cached and refreshed only when real inputs change. - Prefer narrow state propagation over broad root-view invalidation.
- Split large screens into smaller subviews when a local interaction should not refresh the whole feature surface.
- Scope
GeometryReaderand similar layout readers to the smallest area that truly needs layout feedback. - Avoid unnecessary implicit animation in editing-heavy screens.
- Move preparation work off the main thread unless it is strictly UI-only.
- Avoid synchronous file, network, and serialization work on the main thread.
- Batch persistence work where possible instead of writing repeatedly.
- Do not redraw large surfaces because of selection-only state when the content itself is unchanged.
- Avoid repeated layout and drawing work for hidden or non-visible content.
- Prefer stable cached derived data for task lists, lane groupings, timeline ranges, and report summaries.
Every meaningful UI change should pass this checklist before merge:
-
Define the hot interaction. Example: planner cell focus, agile drag/drop, gantt resize, status task selection.
-
Identify the expected performance class.
- discrete interaction
- continuous interaction
- background work
- launch
- persistence / I/O
-
Check update scope.
- Which
@State,@Binding, or observable values change? - Which views recompute because of that change?
- Can the dependency be narrowed?
- Which
-
Check derived work.
- Any
filter,sorted,reduce, grouping, decoding, or formatter creation on the view path? - Can it be cached or precomputed?
- Any
-
Check layout coupling.
- Does
GeometryReader,ScrollViewReader, or custom layout code observe more than necessary?
- Does
-
Check persistence and background work.
- Any file writes, JSON/plist writes, or long calculations on the main thread?
-
Measure before and after.
- SwiftUI instrument for view update cost/frequency
- Time Profiler or Animation Hitches as needed
- Build or test verification for regressions
Use the right tool for the suspected problem:
- SwiftUI update cost/frequency:
- SwiftUI instrument
- main-thread hangs:
- Time Profiler
- Hangs or responsiveness tools where applicable
- motion issues:
- Animation Hitches
- launch:
- App Launch template
- disk writes:
- File Activity
- energy:
- Energy instruments / Organizer metrics
Treat these screens as first-class performance surfaces:
Plan BuilderAgile BoardGantt ChartStatus CenterDashboardWorkloadSchedule
These surfaces must not regress due to unrelated changes.
Flag a change for performance review if it introduces any of the following:
- new root-level computed collections on large models
filterorsortedchains inside views that run on every local state change- repeated formatter creation on hot paths
- selection or focus state living too high in the tree
- heavy
onChangework with broad triggers - broad
@Bindinginvalidation on large editors without cached derivations - extra animation around dense editing workflows
When a performance issue is fixed:
- document the hot interaction in the remediation plan
- keep the derived-data or state-isolation improvement in place
- add measurement notes or tests where practical
- do not remove instrumentation or caching without replacement evidence
No feature is complete if it makes the app feel less native, less immediate, or less stable under interaction.