I was playing around with Observations in an attempt to create a nice way to pass a sequence around and saw some unexpected behaviour. The behaviour occurred intermittently and only when there were multiple Observations to a single property. Probably easiest to show with code and the log:
Code:
@Observable
class AutoCounter {
var value: Int = 0
init() {
Task {
while true {
try! await Task.sleep(for: .seconds(1))
value += 1
}
}
}
}
class Emitter<T> {
let sequence: any AsyncSequence<T, Never>
var changeHandler: ((T) -> Void)?
init(sequence: any AsyncSequence<T, Never>) {
self.sequence = sequence
Task {
for await value in sequence {
changeHandler?(value)
}
}
}
}
extension Emitter {
convenience init(emit: @Sendable @escaping () -> T) {
let valueObservations = Observations(emit)
self.init(sequence: valueObservations)
}
convenience init<Object: Observable>(object: Object, property: KeyPath<Object, T>) {
let valueObservations = Observations { object[keyPath: property] }
self.init(sequence: valueObservations)
}
}
func example() {
let counter = AutoCounter()
let emitterClosure = Emitter { counter.value }
emitterClosure.changeHandler = { print("Emitter 1: \($0)") }
let emitterKP = Emitter(object: counter, property: \.value)
emitterKP.changeHandler = { print("Emitter 2: \($0)") }
}
In summary, AutoCounter has an observable value property that updates every second. There are two instances of the Emitter class which both observe the value of the same AutoCounter instance. When the Emitter observes a change they call their changeHandler which are configure to print the new value. I would expect each Emitter to print an ever increasing message but that doesn’t always happen. The log explains what’s actually happening …
Log:
// ...
Emitter 1: 12
Emitter 2: 12
Emitter 2: 13
Emitter 1: 13
Emitter 1: 14
Emitter 2: 14
Emitter 1: 15
Emitter 2: 14 // Unexpected, would have expected `15`
Emitter 2: 16
Emitter 1: 16
Emitter 1: 17
Emitter 2: 17
Emitter 1: 18
Emitter 2: 18
Emitter 1: 19
Emitter 2: 19
Emitter 2: 19 // Unexpected, would have expected `20`
Emitter 1: 20
Emitter 2: 21
Emitter 1: 21
The logs show that emitter 2 received the same value twice. The order of when the emitters change handlers’ are invoked looks to be undefined, which isn’t necessarily wrong but may be related.
Is this expected behaviour or a bug? If it’s expected then what’s the advice regarding multiple Observations?
This was ran on the iOS Simulator with Xcode 26.3.0 on MacOS 15.7.4.