Swift Combine:这些多播功能有什么作用,我该如何使用它们?

时间:2019-07-06 23:22:27

标签: swift ios13 combine macos-catalina

在遇到一些合并问题时,我遇到了https://developer.apple.com/documentation/combine/publisher中的“使用多个订户”部分:

func multicast<S>(() -> S) -> Publishers.Multicast<Self, S>

func multicast<S>(subject: S) -> Publishers.Multicast<Self, S>

但是,当我尝试确认我的假设,即发送给多个订户时将需要多播时,我发现尝试使用该游乐场代码(从https://github.com/AvdLee/CombineSwiftPlayground/blob/master/Combine.playground/Pages/Combining%20Publishers.xcplaygroundpage/Contents.swift修改)时并不必要(运行于10.14)。 Xcode 11.0 beta 3(11M362v)中的5):

enum FormError: Error { }

let usernamePublisher = PassthroughSubject<String, FormError>()
let passwordPublisher = PassthroughSubject<String, FormError>()

let validatedCredentials = Publishers.CombineLatest(usernamePublisher, passwordPublisher)
    .map { (username, password) -> (String, String) in
        return (username, password)
    }
    .map { (username, password) -> Bool in
        !username.isEmpty && !password.isEmpty && password.count > 12
    }
    .eraseToAnyPublisher()

let firstSubscriber = validatedCredentials.sink { (valid) in
    print("First Subscriber: CombineLatest: Are the credentials valid: \(valid)")
}

let secondSubscriber = validatedCredentials.sink { (valid) in
    print("Second Subscriber: CombineLatest: Are the credentials valid: \(valid)")
}

// Nothing will be printed yet as `CombineLatest` requires both publishers to have send at least one value.
usernamePublisher.send("avanderlee")
passwordPublisher.send("weakpass")
passwordPublisher.send("verystrongpassword")

此打印:

First Subscriber: CombineLatest: Are the credentials valid: false
Second Subscriber: CombineLatest: Are the credentials valid: false
First Subscriber: CombineLatest: Are the credentials valid: true
Second Subscriber: CombineLatest: Are the credentials valid: true

因此,似乎不需要多播即可寻址多个订户。还是我错了?

那么,这些多播功能有什么作用,我将如何使用它们?一些示例代码会很好。

谢谢

Lars

2 个答案:

答案 0 :(得分:2)

PassthroughSubject并不是一个很好的示例,因为它是一个类,可以为您提供参考语义。因此,在一个简单的情况下,只要主题发出一个,两个订阅者就可以直接订阅它并同时接收相同的值。

但这是一个更好的测试用例(受Cocoa With Love的讨论启发):

    let pub1 = Timer.publish(every: 1, on: .main, in: .default).autoconnect()
    let sub = CurrentValueSubject<Int,Never>(0)
    let scan = sub.scan(10) {i,j in i+j}
    pub1.sink { _ in let i = sub.value; sub.value = i+1 }.store(in:&storage)
    scan.sink { print("a", $0) }.store(in:&storage)
    delay(3) {
        scan.sink { print("b", $0) }.store(in:&self.storage)
    }

当第二个sink作为该管道的新订阅者出现时,得出的结果肯定很奇怪:

a 10
a 11
a 13
a 16
b 13
a 20
b 17
a 25
b 22
a 31
b 28
a 38
b 35

接收器ab的编号序列彼此不同,这实际上是因为scan是一个结构。如果我们希望他们获得相同的数字,则可以使用多播:

    let scan = sub.scan(10) {i,j in i+j}.multicast {PassthroughSubject()}.autoconnect()

产生

a 10
a 11
a 13
a 16
a 20
b 20
a 25
b 25

这是连贯的。

但是, still 并不能证明您需要multicast,因为您可以通过说.share()来完成同一件事。我不清楚multicastshare之间的区别。

答案 1 :(得分:0)

An answer/reference from the swift forums意味着多播方法旨在基于.share()运算符。来自Philippe's post

  

在这种情况下,它用于将上游连接到PassthroughSubject,然后自动连接。通常,当订阅者收到订阅时,它将取消第一个订阅之后的所有其他订阅,多播会对此行为进行转义操作并处理多个订阅。

在实践中,如果您想通过组合中的多个管道拆分流和多播事件更新,则似乎最实用的方法是创建一个@Published属性,让任何上游管道使用.assign()或在内部进行更新.sink(),然后通过@Published属性与订阅者建立其他管道。