# SE-0475: Transactional Observation of Values

**URL:** <https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224>\
**Category:** Proposal Reviews\
**Created:** [April 11, 2025, 1:56am UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224 "2025-04-11T01:56:25Z")\
**Posts on this page:** 14\
**Page:** 4

<div class="post-metadata">

**Author:** ![mlemoine](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mlemoine/32/40373_2.png) [@mlemoine](https://forums.swift.org/u/mlemoine)\
**Post date:** [May 15, 2025, 12:22pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/61 "2025-05-15T12:22:40Z")

</div>

> [@Philippe\_Hausler](#):
>
> That particular example shows that a value will be coalesced if timing is denied to the iteration. The last value is only the last printed value that the program emits, further values would be emitted if the program was permitted to run further.

I feel this should be strongly emphasized in the text accompanying the example. Or better yet, the example should be rewritten so it actually exhibits the consistency part of the eventual consistency.

As it currently stands, the specification and the example seem to conflict with each other. Experience has showed me, times and times again, that this exact kind of appearance of conflict in documentation creates confustion and strongly shakes confidence in using or recommending the feature.

Given how many people have strongly argued this point and how many times, I think the feeling is strongly shared.

Please note that this isn't a concern towards the current (or any) implementation at all, but squarely with the wording of the design and specification. However, when the specification is unclear or confusing, devs tend to fallback to poking and proding the implementation. Which is probably, in part, what's happening here as well.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [May 15, 2025, 2:10pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/62 "2025-05-15T14:10:39Z")

</div>

On a different topic: I wonder how this proposal interacts with [SE-0472: Starting tasks synchronously from caller context](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0472-task-start-synchronously-on-caller-context.md).

In the sample code below, are we guaranteed that "Fresh value" is printed before `doSomethingElse()` starts?

```swift
// Assume everything has the same isolation
func startObservation() {
  let values = Observations { ... }
  Task.immediate {
    for await value in values {
      print("Fresh value", value")
    }
  }
}

startObservations()
// <- Is 'Fresh value' already printed, here?
doSomethingElse()

```

If the answer is yes, this is very cool. If not, do we know what prevents it from being possible?

---

<div class="post-metadata">

**Author:** ![mtsrodrigues](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/mtsrodrigues/32/34169_2.png) [@mtsrodrigues](https://forums.swift.org/u/mtsrodrigues)\
**Post date:** [May 15, 2025, 3:12pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/63 "2025-05-15T15:12:28Z")

</div>

> [@gwendal.roue](#):
>
> In the sample code below, are we guaranteed that "Fresh value" is printed before `doSomethingElse()` starts?

I tried the following using May 14, 2025 toolchain:

```swift
@Observable
class Model {
    var count = 0
}

extension Model {
    @MainActor static var shared = Model()
}

@MainActor
func startModelObservation() {
    print(#function)
    let values = Observations { Model.shared.count }
    if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
        Task.immediate {
            for await value in values {
                print("Fresh value: \(value)")
            }
        }
    }
}

func doSomethingElse() {
    print(#function)
}

startModelObservation()
doSomethingElse()

```

Prints:

```console
startModelObservation()
Fresh value: 0
doSomethingElse()

```

---

<div class="post-metadata">

**Author:** ![critz](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/critz/32/11603_2.png) [@critz](https://forums.swift.org/u/critz)\
**Post date:** [May 26, 2025, 12:49am UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/64 "2025-05-26T00:49:45Z")

</div>

Correct me if I’m wrong, but the concern for the lack of back pressure is not addressed here nor in the GitHub code.

I understand the need to balance simplicity with utility. But I worry that we’re not setting users up for success by providing crucial use cases.

---

<div class="post-metadata">

**Author:** ![LucasVanDongen](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/lucasvandongen/32/32387_2.png) [@LucasVanDongen](https://forums.swift.org/u/LucasVanDongen)\
**Post date:** [June 5, 2025, 4:33pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/65 "2025-06-05T16:33:17Z")

</div>

I just kicked the tires a bit using the May the 14th Paciage code. This is the code I have:

```swift
        let names = Observations {
            self.model.currentValue
        }
 
        Task.detached {
            for await name in names {
                print("Task 1: \(name?.rawValue ?? "waiting")")
            }
        }
 
        Task.detached {
            for await name in names {
                print("Task 2: \(name?.rawValue ?? "waiting")")
                Task { @MainActor in
                    self.messageLabel.stringValue = name?.rawValue ?? "waiting"
                }
            }
        }
 
        Task.detached {
            for await name in names {
                print("Task 3: \(name?.rawValue ?? "waiting")")
            }
        }

```

- For every change of `currentValue`, it prints 4 times
- One of the tasks prints twice
- It is never the same one, but iterates over the task as far as I can see
- It doesn’t usually start at the same task

---

<div class="post-metadata">

**Author:** ![malhal](https://avatars.discourse-cdn.com/v4/letter/m/838e76/32.png) [@malhal](https://forums.swift.org/u/malhal)\
**Post date:** [June 9, 2025, 11:09am UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/66 "2025-06-09T11:09:14Z")

</div>

Regarding:

> The expectation of observing a property from a type as an AsyncSequence is that multiple iterations of the same sequence from multiple tasks will emit the same values at the same iteration points.

I would instead expect an exception since sequences are not intended to be used twice. Haven't we discussed this before?

---

<div class="post-metadata">

**Author:** ![CrystDragon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/crystdragon/32/10461_2.png) [@CrystDragon](https://forums.swift.org/u/CrystDragon)\
**Post date:** [June 10, 2025, 7:07am UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/67 "2025-06-10T07:07:13Z")

</div>

One of the sessions of WWDC 25 mentioned this proposal and I wan't to check my understanding on a specific detail.

I've convinced myself that I should always call `Observations.init` in the same isolation domain where I would later mutate the target `Observable`.

@Philippe_Hausler can you kindly clarify if this is the supposed way to use the new APIs. If it is, can you add some notes in the documentation (since such requirements cannot be expressed in the type system)?

---

<div class="post-metadata">

**Author:** ![Philippe\_Hausler](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/philippe_hausler/32/22569_2.png) [@Philippe\_Hausler](https://forums.swift.org/u/Philippe_Hausler)\
**Post date:** [June 10, 2025, 7:17pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/68 "2025-06-10T19:17:19Z")

</div>

That is a decent pattern to follow - not doing so would be considerably harder to enforce any sort of consistency. So it isn't impossible to use in another way, you just end up facing other issues with your `@Observable` type that might be more tricky to reason about; particularly when it comes to the values that are produced from the sequence and if there might be cases where some events are skipped to the eventual changes that occur; e.g. does it create a larger or finer grained transaction?

The one promise that is made; the isolation for the init will be the same isolation as the closure is invoked.

---

<div class="post-metadata">

**Author:** ![KeithBauerANZ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/keithbaueranz/32/27897_2.png) [@KeithBauerANZ](https://forums.swift.org/u/KeithBauerANZ)\
**Post date:** [June 10, 2025, 11:28pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/69 "2025-06-10T23:28:02Z")

</div>

I know I'm (too!) late to the party, but I had cause to look deeper into this recently and I realized a couple things:

- [As discussed in the ProgressReporter thread](https://forums.swift.org/t/review-2nd-sf-0023-progressreporter-progress-reporting-in-swift-concurrency/80284/8), the current implementation has a shortcoming where if `withObservationTracking` discovers that the `emit` closure observes no properties (and will therefore never be run again) the sequence does not finish.
- As [discussed in the ProgressReporter thread](https://forums.swift.org/t/review-2nd-sf-0023-progressreporter-progress-reporting-in-swift-concurrency/80284/15), `withObservationTracking` inherently creates a window where _concurrent_ changes are _not_ tracked. Observation really wants to be able to guarantee that `Observable` types are either global-actor-isolated or non-Sendable, which the type system can't represent.
- `withObservationTracking` doesn't provide an option to cancel an established observation. This sequence actually mitigates this somewhat because you can cancel the iterating task, but it's still the case that a long-lived object which many people begin observing, but which never changes, leaks memory through an ever-increasing set of `observations` and `lookups`.

I don't see how to avoid the latter two issues without a replacement for `withObservationTracking` that creates a _persistent_ observation (avoiding windows where concurrent mutations aren't tracked), and allows that observation to be explicitly cancelled (freeing up its resources on the observed objects).

---

<div class="post-metadata">

**Author:** ![jamieQ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jamieq/32/5117_2.png) [@jamieQ](https://forums.swift.org/u/jamieQ)\
**Post date:** [June 11, 2025, 12:35pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/70 "2025-06-11T12:35:05Z")

</div>

as i've found myself somewhat mentally involved with this feature, i'll endeavor to respond to these:

> [@KeithBauerANZ](#):
>
> [As discussed in the ProgressReporter thread](https://forums.swift.org/t/review-2nd-sf-0023-progressreporter-progress-reporting-in-swift-concurrency/80284/8), the current implementation has a shortcoming where if `withObservationTracking` discovers that the `emit` closure observes no properties (and will therefore never be run again) the sequence does not finish.

i raised this [in the pitch thread](https://forums.swift.org/t/pitch-transactional-observation-of-values/78315/89), and it was [deemed](https://forums.swift.org/t/pitch-transactional-observation-of-values/78315/94) to be 'pretty much a case of bad input yielding bad results', and providing feedback that this may have occurred was deferred as a possible 'future enhancement'. seems like it is a thing that will (and probably does) happen in practice to me though.

> [@KeithBauerANZ](#):
>
> As [discussed in the ProgressReporter thread](https://forums.swift.org/t/review-2nd-sf-0023-progressreporter-progress-reporting-in-swift-concurrency/80284/15), `withObservationTracking` inherently creates a window where _concurrent_ changes are _not_ tracked.

i think a kind-of related question was also covered in the linked posts above, and my reading of the response is that it's the clients' responsibility to ensure there's no 'tearing' potential for any `Sendable` values. IIUC though, the point you surfaced is a bit of a deeper issue with the current implementation of `withObservationTracking`, in that the access tracking and tracking installation are perhaps not themselves 'transactional' and may miss changes to tracked values that occur in-between the generation of the access information and the installation of the `onChange` handler. that seems like a problem.

> [@KeithBauerANZ](#):
>
> `withObservationTracking` doesn't provide an option to cancel an established observation. This sequence actually mitigates this somewhat because you can cancel the iterating task, but it's still the case that a long-lived object which many people begin observing, but which never changes, leaks memory through an ever-increasing set of `observations` and `lookups`.

this was also noted weeks ago on the [implementation PR](https://github.com/swiftlang/swift/pull/79817#discussion_r2104280694), but the PR has now been merged without that seemingly having been addressed.

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [June 11, 2025, 7:21pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/71 "2025-06-11T19:21:17Z")

</div>

> [@KeithBauerANZ](#):
>
> As [discussed in the ProgressReporter thread](https://forums.swift.org/t/review-2nd-sf-0023-progressreporter-progress-reporting-in-swift-concurrency/80284/15), `withObservationTracking` inherently creates a window where _concurrent_ changes are _not_ tracked. Observation really wants to be able to guarantee that `Observable` types are either global-actor-isolated or non-Sendable, which the type system can't represent.

This looks like a real problem in the current implementation, but it isn't inherent to the design and should be fixable.

---

<div class="post-metadata">

**Author:** ![KeithBauerANZ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/keithbaueranz/32/27897_2.png) [@KeithBauerANZ](https://forums.swift.org/u/KeithBauerANZ)\
**Post date:** [June 11, 2025, 7:59pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/72 "2025-06-11T19:59:24Z")

</div>

> [@John\_McCall](#):
>
> This looks like a real problem in the current implementation, but it isn't inherent to the design and should be fixable.

I guess this could be fixed by having the `access` method on `ObservationRegistrar` pull the under-construction observation out of a task-local & install the tracking _for that property_ synchronously _before_ the access. You'd then also have to hold off calling `onChange` until after closure returns, and have an explicit check when it does for preexisting concurrent mutations.

Fixing this would also mean that mutations of observed properties _by_ the first closure to `withObservationTracking` result in calls to `onChange`, which… might be worthy of a run-time warning?

I've thought a few times that the window _between_ calls to `withObservationTracking` was a problem too, but it isn't — the requirement is that repeated calls to it ensure that the first closure always eventually sees the "final" value of the observed properties. Mutations that occur in the window between calls will be seen on the next call, concurrency doesn't even interfere there.

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [June 11, 2025, 8:12pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/73 "2025-06-11T20:12:26Z")

</div>

> [@KeithBauerANZ](#):
>
> I guess this could be fixed by having the `access` method on `ObservationRegistrar` pull the under-construction observation out of a task-local & install the tracking _for that property_ synchronously _before_ the access.

That's probably the easiest way to handle it, yes.

> [@KeithBauerANZ](#):
>
> I've thought a few times that the window _between_ calls to `withObservationTracking` was a problem too, but it isn't — the requirement is that repeated calls to it ensure that the first closure always eventually sees the "final" value of the observed properties. Mutations that occur in the window between calls will be seen on the next call, concurrency doesn't even interfere there.

Right. You can completely tear down your observation state and rebuild it from scratch during a subsequent call to `withObservationTracking`, and that's fine; at worst it results in updates being coalesced, which is permitted (and unavoidable).

---

<div class="post-metadata">

**Author:** ![KeithBauerANZ](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/keithbaueranz/32/27897_2.png) [@KeithBauerANZ](https://forums.swift.org/u/KeithBauerANZ)\
**Post date:** [July 27, 2025, 11:52pm UTC](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224/74 "2025-07-27T23:52:05Z")

</div>

I've filed [#83359](https://github.com/swiftlang/swift/issues/83359) for the concurrent modification bug in `withObservationTracking`

[Previous page](https://forums.swift.org/t/se-0475-transactional-observation-of-values/79224.md?page=3)
