Swift 结合创建自定义主题

Swift Combine create a custom Subject

我想要一个 Subject(类似于 CurrentValueSubject),我可以将其发布到其中,但这会验证我发送的值。例如,我想验证输入是否强制介于一系列值之间,例如 1 和 10。如果高于最大值,则传递最大值,如果低于最小值,则传递最小值。

请不要告诉我在订阅代码上过滤结果,因为那是我试图避免的。那次重复。

伪代码为:

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)

我想生成:

5
1
5
10

显然 CurrentValueSubject 我无法达到那种效果。 我试图创建自己的自定义主题,但我似乎无法让它发挥作用。

有些事情告诉我,我应该以不同的方式看待我的问题,因为我想这太容易了,不需要自定义 Subject

用例:

我有一个设置 class,它在设置屏幕和其他任何地方更新,当值发生变化时,我希望屏幕做出相应的反应。 ValidatedValueSubject 存在于这个 Settings 对象中。

设置需要公开 Subject,这样任何屏幕都可以对 属性 的更改做出反应。

我的自定义方法Subject如下:

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([=13=], 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.
    }
}

你可以包装一个 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
}

并像这样使用它:

let subject = MySubject<Int, Never>(initialValue: 5) { max(1, min([=11=], 10)) }
let ticket = subject.sink { print("value: \([=11=])") }
subject.send(-10)
subject.send(5)
subject.send(15)

输出:

value: 5
value: 1
value: 5
value: 10

欣赏包装主题的学术方面。然而,从 objective 的角度来看,我将提供一个替代方案 - 我将使用 map 来强制值进入定义的边界,而不是过滤器。

import Combine

let subject = CurrentValueSubject<Int, Never>(5)
subject.map { max(1, min([=10=], 10)) }.sink {
    print("value: \([=10=])")
}

subject.send(-10)
subject.send(5)
subject.send(15)

与接受的答案相同的输出。

value: 5
value: 1
value: 5
value: 10