Skip to content

Instantly share code, notes, and snippets.

@NikolaiRuhe
Last active August 15, 2025 15:00
Show Gist options
  • Save NikolaiRuhe/de31b999b8c8c58c65e4c2e8724dda74 to your computer and use it in GitHub Desktop.
Save NikolaiRuhe/de31b999b8c8c58c65e4c2e8724dda74 to your computer and use it in GitHub Desktop.
A property wrapper that creates an AsyncStream yielding values when the property is set.
import os
@propertyWrapper
public struct Streaming<Wrapped: Sendable>: Sendable {
public var wrappedValue: Wrapped {
get { value }
set {
value = newValue
let continuations = protectedContinuations.withLock { $0.values }
for continuation in continuations {
continuation.yield(newValue)
}
}
}
public var projectedValue: Stream {
get {
let id = nextID
let (stream, continuation) = Stream.makeStream(bufferingPolicy: bufferingPolicy)
protectedContinuations.withLock { $0[id] = continuation }
continuation.onTermination = { [protectedContinuations] _ in
protectedContinuations.withLock { $0[id] = nil }
}
return stream
}
}
public init(wrappedValue: Wrapped, bufferingPolicy: Stream.Continuation.BufferingPolicy = .bufferingNewest(1)) {
self.value = wrappedValue
self.bufferingPolicy = bufferingPolicy
}
public typealias Stream = AsyncStream<Wrapped>
private var value: Wrapped
private let bufferingPolicy: Stream.Continuation.BufferingPolicy
private let protectedContinuations: OSAllocatedUnfairLock<[Int: Stream.Continuation]> = .init(initialState: [:])
}
fileprivate var nextID: Int { _nextID.withLock { id in defer { id += 1 }; return id } }
fileprivate let _nextID: OSAllocatedUnfairLock<Int> = .init(initialState: 42)
@NikolaiRuhe
Copy link
Author

Usage:

class BluetoothManager {
    @Streaming
    var bluetoothState: CBManagerState = .unknown

    func waitForBluetoothToPowerOn() async {
        for await state in $bluetoothState where state == .poweredOn {
            break
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment