触发CombineLatest以在Combine中传播初始值

时间:2019-06-30 08:57:20

标签: swift reactive-programming combinelatest combine

我有两个String发布者和一个返回AnyPublisher的计算属性。逻辑很简单,但我想知道是否有任何传播初始值的方法。我认为这应该是可行的,因为发布者具有初始价值。

在VC中,我正在从ViewModel(从textField)向发布者分配新值。

firstTextField.addTarget(self, action: #selector(firstTextFieldDidChange(_:)), for: .editingChanged)
secondTextField.addTarget(self, action: #selector(secondTextFieldDidChange(_:)), for: .editingChanged)

@objc private func firstTextFieldDidChange(_ textField: UITextField) {
 viewModel.firstPublisher = textField.text ?? ""
}
@objc private func secondTextFieldDidChange(_ textField: UITextField) {
 viewModel.secondPublisher = textField.text ?? ""
}

然后将发布者(combineLatest)分配给我的按钮:

_ = viewModel.validatedText
   .receive(on: RunLoop.main)
   .assign(to: \.isEnabled, on: button)

在VM中,我有两个发布者:

@Published var firstPublisher: String = ""
@Published var secondPublisher: String = ""

和CombineLatest:

var validatedText: AnyPublisher<Bool, Never> {
    return Publishers.CombineLatest($firstPublisher, $secondPublisher) {
        return !($0.isEmpty || $1.isEmpty)
        }.eraseToAnyPublisher()
}

validatedText仅在我开始在两个文本字段中键入时才开始发布新值。我尝试在VM的init中分配一些新值(例如,分配给第一台和第二台Publisher),但也没有用。有没有办法做到这一点,否则我将不得不设置按钮的初始状态(禁用它)而不使用合并?

1 个答案:

答案 0 :(得分:2)

不幸的是,这似乎只是@Published的行为,但是您可以在生成的Publisher中通过添加一个初始值来解决此问题:

var validatedText: AnyPublisher<Bool, Never> {
    let validate: (String, String) -> Bool = {
        !($0.isEmpty || $1.isEmpty)
    }
    return Publishers.CombineLatest($firstPublisher, $secondPublisher, transform: validate)
        .prepend(validate(firstPublisher, secondPublisher))
        .eraseToAnyPublisher()
}

相反,如果您愿意采用这种方法,则编写自己的属性委托来获得所需的行为是相当简单的:

import Combine

@propertyDelegate
struct InitialPublished<Value> : Publisher {
    typealias Output = Value
    typealias Failure = Never

    private let subject: CurrentValueSubject<Output, Failure>

    var value: Value {
        set { subject.value = newValue }
        get { subject.value }
    }

    init(initialValue: Value) {
        subject = CurrentValueSubject(initialValue)
    }

    func receive<S>(subscriber: S) where S: Subscriber, Value == S.Input, Failure == S.Failure {
        subject.receive(subscriber: subscriber)
    }
}