Both CurrentValueSubject
and @Published.Publisher
(retrieved via $ from an ObservableObject property) immediately send a notification with the current value when a new subscriber is added (verified with this example).
Is there a way to require this behaviour with a protocol?
For example, if you offer an initializer that requires to pass a publisher, one would use AnyPublisher
here:
init(settings: AnyPublisher<Settings, Never>) {
// ...
}
This would allow to be sneaky and pass in a PassthroughSubject
erased to AnyPublisher
. Is there a way to prevent this that would allow to pass in both a CurrentValueSubject
or an @Published
property? (something like a AnyValuePublisher
?)
There is no way to know up-ahead which Publisher
will replay its latest value vs. which will not (since a custom publisher could do the same, too).
The only thing I can think of in this specific use case is a ghost protocol to only mark these two types for this use case:
protocol ReplayingPublisher: Publisher {}
extension CurrentValueSubject: ReplayingPublisher {}
extension Published.Publisher: ReplayingPublisher {}
struct MyObject {
init<P: ReplayingPublisher>(publisher: P) {
// P is only one of these two possible options
}
}
Below you will find implementation that is what you are looking for:
// MARK: - ValuePublisher
public protocol ValuePublisher: Publisher {
var value: Output { get }
}
// MARK: - AnyValuePublisher
public struct AnyValuePublisher<Output, Failure>: ValuePublisher where Failure: Error {
nonisolated public var value: Output {
box.value
}
private let box: PublisherBoxBase<Output, Failure>
public init<T: ValuePublisher>(_ valuePublisher: T) where T.Output == Output, T.Failure == Failure {
if let erased = valuePublisher as? AnyValuePublisher<Output, Failure> {
self.box = erased.box
} else {
self.box = PublisherBox(base: valuePublisher)
}
}
nonisolated public func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input {
box.receive(subscriber: subscriber)
}
}
// MARK: - AnyValuePublisher > PublisherBox
extension AnyValuePublisher {
private class PublisherBoxBase<Output, Failure: Error>: ValuePublisher {
internal var value: Output {
fatalError("abstract method")
}
internal init() {}
internal func receive<Downstream: Subscriber>(subscriber: Downstream)
where Failure == Downstream.Failure, Output == Downstream.Input {
fatalError("abstract method")
}
}
private final class PublisherBox<PublisherType: ValuePublisher>: PublisherBoxBase<PublisherType.Output, PublisherType.Failure> {
internal let base: PublisherType
internal override var value: PublisherType.Output {
base.value
}
internal init(base: PublisherType) {
self.base = base
}
internal override func receive<Downstream: Subscriber>(subscriber: Downstream) where Failure == Downstream.Failure, Output == Downstream.Input {
base.receive(subscriber: subscriber)
}
}
}
extension Publisher where Self: ValuePublisher {
public func eraseToAnyValuePublisher() -> AnyValuePublisher<Output, Failure> {
AnyValuePublisher(self)
}
}
extension CurrentValueSubject: ValuePublisher {}
Sample usage would be:
let publisher = CurrentValueSubject<Int, Never>(1).eraseToAnyValuePublisher()
let someValue: Int = publisher.value
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With