Home > Enterprise >  Read only CurrentValueSubject?
Read only CurrentValueSubject?

Time:10-16

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.

CodePudding user response:

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()
}

CodePudding user response:

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.

CodePudding user response:

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 currentValueSubject the module can freely compose it internally, while the outside world can only consume the values.

  • Related