[Accepted with revision] SE-0395: Observability

For the record, this review – or any of the previous proposal threads – are still yet to offer a rationale as to why a purported generalised mechanism for observation generates events exclusively on a willSet basis.

Whilst this fulfils the very specific requirements of Swift UI, for a generalised observation mechanism this seems highly unusual and is a major limitation.

The workaround of re-creating a didSet notification through an async Task will quickly be revealed as an anti-pattern for the reasons expressed below.

For context, here's some further detail on the issue as discussed in the latest proposal thread:


The willSet variant is great for allowing SwiftUI to perform its 'before changes' render pass and schedule its 'after changes' render pass for the end of the current event loop cycle, but for anything else that needs to happen between these two points – we're stuck.

Really, the onChange parameter of the withObservationTracking(_:onChange:) function should be called willChange to better reflect the semantics of the API.

A participant in the old pitch thread laid out their [questions surrounding] the current API:

As I summarised in the pitch thread, the two recommendations we have right now are to a) use computed properties, or b) use this pattern:

However, method A suffers from unnecessary view refreshes, while method B suffers from an async hop i.e the observation event happens after the 'after changes' SwiftUI snapshot. It misses SwiftUI's deadline for changes in the current transaction. It's also for this reason that an asynchronous sequence based API wouldn't help us here either.

To make this work how we want it, we really need Observable to generate trailing edge (didSet) synchronous notifications.

This would allow dependent observers to make any changes they need to make within SwiftUI's deadline for the current event loop.

It doesn't need to be anything huge for this version of the proposal, something as simple as changing withObservationTracking(_:onChange:) to withObservationTracking(_:willChange:didChange:) could lay the foundation.

45 Likes