Friday, October 31, 2025

Swift 6.2: Observations

Holly Borla:

Swift 6.2 enables streaming transactional state changes of observable types using the new Observations async sequence type. Updates include all synchronous changes to the observable properties, and the transaction ends at the next await that suspends. This avoids redundant UI updates, improves performance, and ensures that your code reacts to a consistent snapshot of the value.

As with notification center messages, this is really part of the macOS 26 frameworks rather than Swift 6.2 itself.

Donny Wals:

Sequences created by Observations will automatically observe all properties that you accessed in your Observations closure. In this case we’ve only accessed a single property so we’re informed whenever count is changed. If we accessed more properties, a change to any of the accessed properties will cause us to receive a new value. Whatever we return from Observations is what our async sequence will output. In this case that’s a string but it can be anything we want. The properties we access don’t have to be part of our return value. Accessing the property is enough to have your closure called, even when you don’t use that property to compute your return value.

[…]

When iterating over our Observations sequence we’ll receive values in our loop after they’ve been assigned to our @Observable model. This means that Observations sequences have “did set semantics” while withObservationTracking would have given us “will set semantics”.

Keith Harrison:

The updates are transactional so multiple synchronous changes arrive as a single updated value.

SE-0475:

Observation was introduced to add the ability to observe changes in graphs of objects. The initial tools for observation afforded seamless integration into SwiftUI, however aiding SwiftUI is not the only intent of the module - it is more general than that. This proposal describes a new safe, ergonomic and composable way to observe changes to models using an AsyncSequence, starting transactions at the first willSet and then emitting a value upon that transaction end at the first point of consistency by interoperating with Swift Concurrency.

[…]

This proposal does not change the fact that the spectrum of APIs may range from favoring AsyncSequence properties to purely @Observable models. They both have their place. However the calculus of determining the best exposition may be slightly more refined now with Observations.

Jared Sinclair:

Apple has effectively deprecated the reigning paradigm of the ObservableObject protocol and @Published properties observed via the Combine framework, but they’ve only partially provided its replacement via the @Observable macro and the withObservationTracking free function. The gaps between the old way and the new way are worth careful consideration.

[…]

Observations is an Apple-provided way for one object to subscribe to long-running changes to some other, @Observable-macro’ed object. It is written to use the AsyncSequence protocol. Despite the fact that withObservationTracking was released in OS 17, Observations has not been back-ported and requires OS 26.

[…]

To implement cancellation you need to wrap the entire thing in a Task, store that task in an instance variable, and determine key points in the lifecycle of your object to cancel that task[…]

[…]

It is important that your Task and your Observations structs weakly-capture both self and whatever object you’re trying to observe. But it’s still easy to get it wrong.

[…]

You can’t access a mutable var property from the deinit. You would need to wrap that property in some kind of synchronization box, like a Mutex, which comes with its own kinds of hassles and boilerplate.

Combine was much more succinct, but it seems like this API is a work in progress so it may get better next year.

Paul Hudson (Mastodon, Hacker News):

There are a handful of important usage notes you should be aware of when using Observations:

  1. It will emit the initial value as well as all future values.
  2. If multiple changes come in at the same time, they might be coalesced into a single value being emitted. For example, if our Task code incremented score twice, the values emitted would go up in 2s.
  3. The AsyncSequence of values being emitted can potentially run forever, so you should put it on a separate task or otherwise handle it carefully.
  4. If you want iteration to stop – to end the loop – you should make the value being observed optional, then set it to nil.

Lucas van Dongen:

I built a simple demo app with both the new and existing stuff.

Previously:

2 Comments RSS · Twitter · Mastodon


I disagree with your statement that the Observations API "is really part of the macOS 26 frameworks rather than Swift 6.2 itself."

The Observation library, including the new Observations API, is part of the Swift toolchain and available on all platforms supported by Swift 6.2 (as far as I know; I just tested it on Linux to confirm that it works). All these APIs went through Swift Evolution review.

It's true that on Apple platforms the Observations API is limited to iOS 26/macOS 26 deployment targets, but not because it's a proprietary Apple API (which it isn't). Rather, it's a consequence of Apple shipping the Swift libraries as part of the OS and most new Swift features that aren't pure compiler features require the updated libraries and/or runtime. (New *functions* added to the standard library can sometimes be back-deployed, but apparently this is much harder to do for new *types*, which was the blocker for back-deploying the Observations API. See a Swift forum post where Philippe Hausler explains why Observations couldn't be back-deployed.)


@Ole Yes, thanks for the correction. It is part of the language rather than Foundation. If this stuff can’t back deploy, I wish they would just let us bundle Swift itself in the app like before ABI stability.

Leave a Comment