Create an AsyncStream from withObservationTracking() function
I needed to create an AsyncStream
to monitor changes to an @Observable
model class’s var and by using withCheckedContinuation
and AsyncStream
‘s unfolding init I came up with this:
let modelIdentifiersDidChange = AsyncStream {
await withCheckedContinuation { continuation in
withObservationTracking {
let _ = model.identifiers // let _ required to fix compilation error.
} onChange: {
continuation.resume() // model.identifiers doesn't contain the new value here yet.
}
}
return model.identifiers // optionally insert the value into the stream
}
The challenge here is withObservationTracking
only watches for a single change but we can wrap that in withCheckedContinuation
which also is designed to only work once, but with AsyncStream’s nifty unfolding init it can monitor for multiple changes. The only ugly thing is the let _
= is required to fix a compilation error.
In case you were interested, here is the model:
@Observable
class Model {
var identifiers = Set<String>()
}
Before I came up with my own solution I searched and found this one, however I didn’t like the DispatchQueue
hack and I think AsyncStream’s unfolding init is a better option.
It can be a little tricky to work with a stream when you require the initial value and support cancellation and deal with the optionality of stream’s next function. I’ve been experimenting with the pattern below, where I don’t actually return the value from the stream but just use it as a notification that there has been a change and use a repeat/do while loop:
let modelIdentifiersDidChange = AsyncStream {
await withCheckedContinuation { continuation in
withObservationTracking {
let _ = model.identifiers // let _ required to fix compilation error.
} onChange: {
continuation.resume() // model.identifiers doesn't contain the new value here yet.
}
}
}
var iterator = modelIdentifiersDidChange.makeAsyncIterator()
repeat
{
let identifiers = model.identifiers
// do actual work
print("task \(identifiers)")
}
while await iterator.next() != nil