You can be doing real work on every model write in Swift Observation even when nobody is observing that property. The two main costs being discussed right now are key-path formation and locking the Observation registrar — paid twice per mutation (willSet + didSet) in the key-path-only path — just to discover there are no observers. (forums.swift.org)
If you suspect “random stalls” during bursts of model mutation (sync, decoding, chat timelines, sensor feeds), you can stop guessing: you can instrument your app to detect “write-only” (never observed) properties in production, and then change your model shape so you stop paying for observation you do not use.
What’s actually costing time today (even when nothing is observing)
The Swift Forums pitch from October 6, 2026 is unusually concrete about where the current overhead comes from when a property is mutated. It calls out that every mutation of an @Observable property calls willSet(_:keyPath:) and didSet(_:keyPath:), and when the registrar only has a key path to identify the property it may still need to:
- form the key path (via
swift_getKeyPath, plus retain/release), and - take the registrar’s lock and hash the key path to look it up,
- and do that twice (willSet + didSet)
…even if it ultimately learns nobody is observing that property. (forums.swift.org)
The same pitch also makes the design intent explicit: “the cost of observation should track what is actually observed”. (forums.swift.org)
There are runtime-only optimisations proposed (lazy tables, cheaper “never observed” checks, fewer allocations), but the pitch is clear about the uncomfortable edge case: if your object is observed for some properties, mutations of other unobserved properties still incur the key-path + lookup machinery, because the registrar has no cheaper identity for the property. (forums.swift.org)
The failure mode in real apps: observed object, unobserved hot path
The common shape in shipping SwiftUI apps is:
- A model object is observed by the UI, but the UI reads only a handful of its properties.
- Background work (network sync, reducers, debouncers, decoders) mutates lots of other properties.
- Those other properties might be internal caches, intermediate state, denormalised fields, logging counters, or “prepared” view data you thought would be helpful.
This is the part founders and heads of engineering tend to miss: the presence of any observation on the object can cause you to pay observation-related per-write costs on properties that are never read by any observer.
The pitch proposes “property indices” so that mutations can cheaply check a per-object bitmask and return early when a property cannot be observed, avoiding taking the registrar lock in the unobserved case. (forums.swift.org)
That is heading in the right direction, but it does not solve your 2026 production issues by itself:
- you may be deploying to older runtimes for a while,
- you may have existing architecture patterns that produce “write-only” properties,
- and you still need a way to prove which parts of the model are never observed so you can safely restructure.
A practical definition: “never observed” vs “not currently observed”
There are two categories you should not mix:
- Not currently observed: nobody is observing it right now (for example the view is off-screen).
- Never observed: across a representative slice of real sessions, nothing ever registers an access for that property.
You care about the second category because it tells you you have model work that is structurally pointless (at least from Observation’s perspective). That is where you can remove cost without changing the UX.
Swift’s Observation system is fundamentally based on tracking property accesses inside withObservationTracking, using thread-local storage to record which properties were read, and then wiring those to an onChange callback. (techblog.lycorp.co.jp)
That detail matters: “observed” in this sense is not “it exists on an observable type”, it is “somebody actually read it in an observation-tracked context”.
Instrumentation approach: measure observation, not mutation
The easiest instrumentation mistake is to count writes. That does not tell you whether anyone cares.
Instead, aim to answer two questions:
- Which properties are being accessed under observation tracking?
- Which properties are being mutated a lot, but never show up in (1)?
You do not need private API to get something useful; you need disciplined plumbing in your own model layer.
Step 1: create a stable “property identity” you can log
Key paths are a natural identity, but they are themselves part of the cost centre. Also, AnyKeyPath is not ideal for shipping analytics volume.
The pitch’s direction (small integer property indices) is a useful mental model even before it is universally available: you want a stable, low-cost identifier per property. (forums.swift.org)
In practice, teams do this by defining per-type enums:
enum UserModelField: UInt16 { case displayName, avatarURL, ... }enum TimelineField: UInt16 { case items, isRefreshing, cacheHits, ... }
Then you ensure every “interesting” access/mutation path can be associated with one of those IDs.
Step 2: record accesses where you control reads
You rarely control what SwiftUI reads, but you usually control the reads that matter:
- computed properties used by views,
- formatters cached in the model,
- derived properties used by lists,
- application logic that reads state to make decisions.
Add a thin wrapper for reads in your model layer (or in view-model glue) that both returns the value and reports an “access” event.
Be strict about scope:
- Do this only for types where performance matters.
- Guard it behind a runtime flag so you can enable it for a percentage of sessions.
- Sample aggressively. You are after “never observed”, not a perfect trace.
Step 3: record mutations on hot properties
On the write side, you again want identifiers, not strings.
The aim is to find:
- properties that are mutated frequently, and
- have a near-zero access count under observation tracking.
This gives you a ranked list of “write-only” candidates.
Step 4: compute “never observed” candidates safely
From production data, mark a property as “never observed” only when:
- you have enough sessions (from the same app version) to be confident, and
- the property has non-trivial write volume, and
- you see accesses to other properties on the same model type (so you know observation is in play).
If the model type itself is never observed, you might simply be looking at background-only state, which is fine.
What to do when you find write-only properties
Once you have a list, the architectural options are usually not exotic. They are about putting mutable state where it belongs.
Option A: stop tracking it
If a property is written but never read by UI or other observers, the simplest answer is often: do not make it observable.
That can mean:
- mark it ignored (or make it non-tracked by design),
- move it to a non-observable helper object,
- or make it local to the subsystem that uses it (not part of the shared model).
The point is not “Observation is slow”. The point is: if nobody observes it, observation is the wrong mechanism.
Option B: split the model by observation boundary
A common anti-pattern is a single “god model” that holds:
- UI-facing state,
- sync state,
- caches,
- derived / formatted display state,
- and telemetry.
Splitting this into:
- a small UI-observed model, and
- a separate internal state object (not observed, or observed by different mechanisms)
…is often the highest leverage fix.
This aligns directly with the pitch’s claim that today the expensive case is “mutating an unobserved property of an observed object”, and that property indices are designed to make that cheaper. (forums.swift.org)
Even if the runtime gets better, splitting still reduces contention and makes performance easier to reason about.
Option C: change event granularity when “the object is read as a whole”
The pitch also calls out that per-property tracking is sometimes the wrong granularity, and proposes a coarser “object-level” resolution and grouping options for events. (forums.swift.org)
You do not need to wait for that to adopt the underlying idea: if your UI always consumes a bundle of fields together, you can model that bundle as a single observable value (or a small number of values), rather than dozens of independently tracked properties.
Trade-off: you may increase redraw work. It is only worth doing when your UI genuinely treats the state as one unit.
Option D: derived values should be derived, not eagerly maintained
A repeated source of write-only properties is “precomputed view state” that is recomputed eagerly on every mutation, but the view only reads it occasionally.
The pitch proposes a dedicated mechanism for derived properties that only recompute when inputs change, and are essentially first-class in Observation. (forums.swift.org)
Even without that language feature, you can implement the discipline:
- store inputs as observable,
- compute derived values lazily when accessed,
- cache with invalidation.
Trade-off: you are building a small caching system. If the derived value is cheap, do not do this.
A checklist for deciding “do not do this”
Instrumentation and model refactors cost real engineering time. Situations where you should probably not chase this:
- The app does not have user-visible stalls and you do not have evidence Observation is on your hot path.
- Your performance problems are dominated by layout, image decoding, database I/O, or networking.
- The model in question is mutated rarely (optimising it will be noise).
- You cannot deploy instrumentation safely (regulated environments, very small user base, no analytics pipeline).
Conversely, it is worth doing when:
- you have bursty mutation (sync, streaming updates) and UI stutters,
- your observed models are large and “multi-purpose”,
- and you can roll out a measurement flag to production.
The goal is not to make Observation “free”; it is to stop paying for work nobody observes.
Where Codeversols fits (briefly)
If you are building or modernising a SwiftUI app, this is the kind of performance work that benefits from an outside team that can move quickly: adding low-risk instrumentation, interpreting the data, and then refactoring model boundaries without breaking UI behaviour. Codeversols does software engineering across mobile, web, AI, cloud and design, and this is a common pattern when teams are scaling a product beyond its initial model layer assumptions.
Close
Swift Observation is improving, and the October 6, 2026 pitch is explicit about current costs: key-path formation plus registrar locking can still happen even when a property has no observers. (forums.swift.org)
You do not need to wait for the runtime to catch up to fix your app today. Instrument for “never observed” properties, treat write-only state as a design smell, and split your models so the observation system only pays for what your product actually observes.



