I would like to have one Subject (similar to CurrentValueSubject) I can publish into, but that validates the value I'm sending. For example, I would like to validate that the input is forced between a range of values, like 1 and 10. If higher than the max, pass the maximum, if lower then the min, pass the minimum.
Please don't tell me to filter the result on the subscribing code because that's what I'm trying to avoid. That duplication.
Pseudo code would be:
let intSubject = ValidatedValueSubject<Int>(value: 5, min: 1, max: 10)
intSubject.sink { value in print(value) }
intSubject.send(-10)
intSubject.send(5)
intSubject.send(15)
I would like this to produce:
5
1
5
10
Obviously with CurrentValueSubject I can't achieve that effect.
I tried to create my own custom Subject but I can't seem to make it work.
Something tells me I should look at my problem differently because I guess this is too easy to need a custom Subject.
The use case:
I have a settings class which is updated on a Settings screen, and everywhere else, when the value change I want the screens to react accordingly. The ValidatedValueSubject lives inside this Settings object.
The Settings need to expose the Subject so any screens can react upon changes to the property.
My approach to the custom Subjectis as follows:
final class QualitySubject: Subject {
public typealias Output = Int
public typealias Failure = Never
public private(set) var value: Output
private let max: Output
private let min: Output
init(value: Output, max: Output, min: Output) {
self.min = min
self.max = max
self.value = value
self.value = validated(value)
}
private func validated(_ value: Output) -> Int {
return max(min, min($0, max))
}
var subscription: [???? QualitySubscription ?????] = []
public func send(_ value: Output) {
self.value = validated(value)
subscription.subscriber.receive(value)
}
public func send(completion: Subscribers.Completion<Failure>) {
print("completion")
}
public func send(subscription: Subscription) {
print("send subscription")
}
public func receive<S>(subscriber: S) where S : Subscriber, S.Failure == Failure, S.Input == Output {
let qualitySubscription = QualitySubscription(value: value, subscriber: subscriber)
subscriber.receive(subscription: qualitySubscription)
// I think I should save a reference to the subscription so I could forward new values afterwards (on send method) but I can't because of generic constraints.
}
}
You can wrap a CurrentValueSubject:
class MySubject<Output, Failure: Error>: Subject {
init(initialValue: Output, groom: @escaping (Output) -> Output) {
self.wrapped = .init(groom(initialValue))
self.groom = groom
}
func send(_ value: Output) {
wrapped.send(groom(value))
}
func send(completion: Subscribers.Completion<Failure>) {
wrapped.send(completion: completion)
}
func send(subscription: Subscription) {
wrapped.send(subscription: subscription)
}
func receive<Downstream: Subscriber>(subscriber: Downstream) where Failure == Downstream.Failure, Output == Downstream.Input {
wrapped.subscribe(subscriber)
}
private let wrapped: CurrentValueSubject<Output, Failure>
private let groom: (Output) -> Output
}
And use it like this:
let subject = MySubject<Int, Never>(initialValue: 5) { max(1, min($0, 10)) }
let ticket = subject.sink { print("value: \($0)") }
subject.send(-10)
subject.send(5)
subject.send(15)
Output:
value: 5
value: 1
value: 5
value: 10
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