Is there a way to create a CurrentValueSubject that is read-only?
So you could sink it publicly, read value publicly, but could only send values to it internally/privately. Want to use it in a library module.
Is there a way to create a CurrentValueSubject that is read-only?
So you could sink it publicly, read value publicly, but could only send values to it internally/privately. Want to use it in a library module.
The best pattern is to have it declared private:
private let _status = CurrentValueSubject<ThisStatus?, Never>(nil)
and expose it through a computed property:
public var status: AnyPublisher<ThisStatus?, Never> {
_status
.eraseToAnyPublisher()
}
I ended up creating a Publisher wrapping a CurrentValueSubject.
This way it can be written internally (to the module), but other modules can only read/subscribe to the wrapping publisher.
public class ReadOnlyCurrentValueSubject<Output, Failure>: Publisher where Failure : Error {
internal let currentValueSubject: CurrentValueSubject<Output, Failure>
public internal(set) var value: Output {
get { currentValueSubject.value }
set { currentValueSubject.value = newValue }
}
public init(_ value: Output) {
currentValueSubject = .init(value)
}
public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Output == S.Input {
currentValueSubject.receive(subscriber: subscriber)
}
}
With having internal access to the
currentValueSubjectthe module can freely compose it internally, while the outside world can only consume the values.
You can write a custom publisher that wraps a CurrentValueSubject, and that exposes the subject only at initialization time. This way, the code that creates the publisher is the only one having access to the subject, and is able to instruct the publisher to emit events.
The new publisher can look like this:
extension Publishers {
public struct CurrentValue<Value, Failure: Error>: Publisher {
public typealias Output = Value
public typealias Subject = CurrentValueSubject<Value, Failure>
public var value: Value { subject.value }
private var subject: Subject
public static func publisher(_ initialValue: Value) -> (Self, Subject) {
let publisher = Self(initialValue)
return (publisher, publisher.subject)
}
private init(_ initialValue: Value) {
subject = Subject(initialValue)
}
public func receive<S>(subscriber: S) where S : Subscriber, Failure == S.Failure, Value == S.Input {
subject.receive(subscriber: subscriber)
}
}
}
, and can be consumed in this fashion:
class MyClass {
// use this to expose the published values in a readonly manner
public let publisher: Publishers.CurrentValue<Int, Never>
// use this to emit values/completion
private var subject: Publishers.CurrentValue<Int, Never>.Subject
init() {
(publisher, subject) = Publishers.CurrentValue.publisher(10)
}
}
This way you have a readonly value publisher, and the only instance that can publish values is the one that instantiates the publisher.
Now, if the internal requirement you specified in the question must be taken ad-literam, then you can change the visibility of the CurrentValue.subject property to be internal, in this case you no longer need the static method.
A good replacement in this case is:
@Published public private(set) var status: ThisStatus? = nil
Use $status to get the underlying publisher.