简体   繁体   English

Swift 合并,只有一个发布者的值发生变化时如何合并发布者和接收者?

[英]Swift Combine, how to combine publishers and sink when only one publisher's value changes?

I have found ways of combining publishers using MergeMany or CombineLatest , but I don't seem to find a solution in my particular case.我已经找到了使用MergeManyCombineLatest组合发布者的方法,但在我的特定情况下我似乎没有找到解决方案。

Example:例子:

class Test {
    @Published var firstNameValid: Bool = false
    @Published var lastNameValid: Bool = false
    @Published var emailValid: Bool = false

    @Published var allValid: Bool = false
}

I want allValid to become false when any of the previous publishers are set to false , and true if all of them are true .当任何以前的发布者设置为false时,我希望allValid变为false ,如果它们都设置为true ,则为true I also don't want to hardcode the list of publishers I am observing since I want a flexible solution, so I want to be able to pass an array of Bool publishers to whatever code I use to do this.我也不想硬编码我正在观察的发布者列表,因为我想要一个灵活的解决方案,所以我希望能够将一组Bool发布者传递给我用来执行此操作的任何代码。

I tried this我试过这个

        let fieldPublishers = [$firstNameValid, $lastNameValid, $emailValid]
        Publishers
            .MergeMany(fieldPublishers)
            .sink { [weak self] values in
                self?.allValid = values.allSatisfy { $0 }
            }
            .store(in: &subscribers)

But this of course doesn't work because I get an array of publishers and not an array of values.但这当然不起作用,因为我得到了一组发布者而不是一组值。 I tried some other ways (forgot which ones) but they only seemed to call sink if I assigned a value during execution to all 3 publishers.我尝试了其他一些方法(忘记了哪些方法),但如果我在执行期间为所有 3 个发布者分配了一个值,它们似乎只会调用sink

In the case of only using 2 publishers I managed to get it working using CombineLatest .在仅使用 2 个发布者的情况下,我设法使用CombineLatest使其工作。

So the question is: Can I have sink triggered when only one of the publishers in an array changes value after instantiation of Test , and then iterate over the values of all the publishers I am observing?所以问题是:当数组中只有一个发布者在Test实例化后更改值时,我是否可以触发接收sink ,然后遍历我正在观察的所有发布者的值?

CombineLatest is indeed correct. CombineLatest 确实是正确的。 You have three publishers so you would use CombineLatest3.您有三个发布者,因此您将使用 CombineLatest3。 In this example, I use CurrentValueSubject publishers instead of @Published , but it's the same principle:在此示例中,我使用 CurrentValueSubject 发布者而不是@Published ,但原理相同:

import UIKit
import Combine

func delay(_ delay:Double, closure:@escaping ()->()) {
    let when = DispatchTime.now() + delay
    DispatchQueue.main.asyncAfter(deadline: when, execute: closure)
}
class ViewController: UIViewController {
    let b1 = CurrentValueSubject<Bool,Never>(false)
    let b2 = CurrentValueSubject<Bool,Never>(false)
    let b3 = CurrentValueSubject<Bool,Never>(false)
    var storage = Set<AnyCancellable>()
    override func viewDidLoad() {
        super.viewDidLoad()

        Publishers.CombineLatest3(b1,b2,b3)
            .map { [$0.0, $0.1, $0.2] }
            .map { $0.allSatisfy {$0}}
            .sink { print($0)}
            .store(in: &self.storage)
        
        // false
        delay(1) {
            self.b1.send(true) // false
            delay(1) {
                self.b2.send(true) // false
                delay(1) {
                    self.b3.send(true) // true
                    delay(1) {
                        self.b1.send(false) // false
                        delay(1) {
                            self.b1.send(true) // true
                        }
                    }
                }
            }
        }
    }
}

Okay, now you may complain, that's okay for a hard-coded three publishers, but I want any number of publishers.好的,现在您可能会抱怨,对于硬编码的三个发布者来说没关系,但我想要任意数量的发布者。 Fine, Start with an array of your publishers: accumulate them one at a time with CombineLatest to form a publisher that produces an array of Bool:好的,从您的发布者数组开始:使用 CombineLatest 一次累积一个以形成一个发布者,该发布者生成一个 Bool 数组:

    let list = [b1, b2, b3] // any number of them can go here!
    let pub = list.dropFirst().reduce(into: AnyPublisher(list[0].map{[$0]})) {
        res, b in
        res = res.combineLatest(b) {
            i1, i2 -> [Bool] in
            return i1 + [i2]
        }.eraseToAnyPublisher()
    }

    pub
        .map { $0.allSatisfy {$0}}
        .sink { print($0)}
        .store(in: &self.storage)

This is technically not an answer to your last question but wouldn't the property allValid make more sense as a computed property?从技术上讲,这不是您最后一个问题的答案,但属性 allValid 作为计算属性是否更有意义?

var allValid: Bool {
   firstNameValid && lastNameValid && emailValid
}

This would make sure, that allValid at all times represents the logical AND for the other three properties.这将确保 allValid 始终代表其他三个属性的逻辑与。 I hope that I have understood the core of your question and this helped.我希望我已经理解了您问题的核心,这对您有所帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM