Skip to content

Instantly share code, notes, and snippets.

@darrarski
Created January 15, 2023 01:18
Show Gist options
  • Save darrarski/b52d3ac80c04c932849fc2e26aa24c15 to your computer and use it in GitHub Desktop.
Save darrarski/b52d3ac80c04c932849fc2e26aa24c15 to your computer and use it in GitHub Desktop.
[swift-concurrency] Async sequence with current value (similar to Combine's CurrentValueSubject).
import Foundation
@dynamicMemberLookup
public actor CurrentValueAsyncSequence<Value>: AsyncSequence where Value: Sendable {
public typealias Element = Value
public init(_ value: Value) {
self.value = value
}
deinit {
continuations.values.forEach { $0.finish() }
}
public private(set) var value: Value {
didSet { continuations.values.forEach { $0.yield(value) } }
}
public subscript<T>(dynamicMember keyPath: KeyPath<Value, T>) -> T {
self.value[keyPath: keyPath]
}
private var continuations = [UUID: AsyncStream<Element>.Continuation]()
nonisolated public func makeAsyncIterator() -> AsyncStream<Value>.Iterator {
let id = UUID()
let stream = AsyncStream<Element> { [weak self] continuation in
Task { [self] in await self?.add(id, continuation) }
continuation.onTermination = { [self] _ in
Task { [self] in await self?.remove(id) }
}
}
return stream.makeAsyncIterator()
}
public func setValue(_ value: Value) {
self.value = value
}
@discardableResult
public func withValue<T>(_ operation: @Sendable (inout Value) throws -> T) rethrows -> T {
var value = self.value
defer { self.value = value }
return try operation(&value)
}
private func add(_ id: UUID, _ continuation: AsyncStream<Element>.Continuation) {
continuations[id] = continuation
continuation.yield(value)
}
private func remove(_ id: UUID) {
continuations[id] = nil
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment