Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Diagnoses SwiftUI performance issues — slow rendering, janky scrolling, high CPU/memory — through code review and Instruments profiling guidance.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/code-smells.md
1# Common code smells and remediation patterns23## Intent45Use this reference during code-first review to map visible SwiftUI patterns to likely runtime costs and safer remediation guidance.67## High-priority smells89### Expensive formatters in `body`1011```swift12var body: some View {13let number = NumberFormatter()14let measure = MeasurementFormatter()15Text(measure.string(from: .init(value: meters, unit: .meters)))16}17```1819Prefer cached formatters in a model or dedicated helper:2021```swift22final class DistanceFormatter {23static let shared = DistanceFormatter()24let number = NumberFormatter()25let measure = MeasurementFormatter()26}27```2829### Heavy computed properties3031```swift32var filtered: [Item] {33items.filter { $0.isEnabled }34}35```3637Prefer deriving this once per meaningful input change in a model/helper, or store derived view-owned state only when the view truly owns the transformation lifecycle.3839### Sorting or filtering inside `body`4041```swift42List {43ForEach(items.sorted(by: sortRule)) { item in44Row(item)45}46}47```4849Prefer sorting before render work begins:5051```swift52let sortedItems = items.sorted(by: sortRule)53```5455### Inline filtering inside `ForEach`5657```swift58ForEach(items.filter { $0.isEnabled }) { item in59Row(item)60}61```6263Prefer a prefiltered collection with stable identity.6465### Unstable identity6667```swift68ForEach(items, id: \.self) { item in69Row(item)70}71```7273Avoid `id: \.self` for non-stable values or collections that reorder. Use a stable domain identifier.7475### Top-level conditional view swapping7677```swift78var content: some View {79if isEditing {80editingView81} else {82readOnlyView83}84}85```8687Prefer one stable base view and localize conditions to sections or modifiers. This reduces root identity churn and makes diffing cheaper.8889### Image decoding on the main thread9091```swift92Image(uiImage: UIImage(data: data)!)93```9495Prefer decode and downsample work off the main thread, then store the processed image.9697## Observation fan-out9899### Broad `@Observable` reads on iOS 17+100101```swift102@Observable final class Model {103var items: [Item] = []104}105106var body: some View {107Row(isFavorite: model.items.contains(item))108}109```110111If many views read the same broad collection or root model, small changes can fan out into wide invalidation. Prefer narrower derived inputs, smaller observable surfaces, or per-item state closer to the leaf views.112113### Broad `ObservableObject` reads on iOS 16 and earlier114115```swift116final class Model: ObservableObject {117@Published var items: [Item] = []118}119```120121The same warning applies to legacy observation. Avoid having many descendants observe a large shared object when they only need one derived field.122123## Remediation notes124125### `@State` is not a generic cache126127Use `@State` for view-owned state and derived values that intentionally belong to the view lifecycle. Do not move arbitrary expensive computation into `@State` unless you also define when and why it updates.128129Better alternatives:130- precompute in the model or store131- update derived state in response to a specific input change132- memoize in a dedicated helper133- preprocess on a background task before rendering134135### `equatable()` is conditional guidance136137Use `equatable()` only when:138- equality is cheaper than recomputing the subtree, and139- the view inputs are value-semantic and stable enough for meaningful equality checks140141Do not apply `equatable()` as a blanket fix for all redraws.142143## Triage order144145When multiple smells appear together, prioritize in this order:1461. Broad invalidation and observation fan-out1472. Unstable identity and list churn1483. Main-thread work during render1494. Image decode or resize cost1505. Layout and animation complexity151