了解 Combine 中的 share()

Understanding share() in Combine

众所周知,通常发布者都是结构化的。如果是 class 会发生什么变化?

假设我们有 1 个发布者发出 1 个值,2 个订阅者订阅它。

let p1 = Just(20)
let s1 = p1.print().sink { _ in }
let s2 = p1.print().sink { _ in }

// s1 - receive value: (20)
// s2 - receive value: (20)

在打印日志中,我们可以看到两个订阅者都获得了值 (20)。

如果我们打开 share() 运算符的文档,我们将看到

share() - Returns a publisher as a class instance.

所以它只是将发布者的语义从值更改为引用。在我们的示例中,我们不会将 p1 publisher 传递给任何函数或分配给任何对象,这就是为什么对我来说 publisher 是结构或 class 没有区别...... 但是如果我添加 share() 运算符行为会有所不同,s2 将不会获得价值。

let p1 = Just(20).share() // !
let s1 = p1.print().sink { _ in }
let s2 = p1.print().sink { _ in }

// s1 - receive value: (20)

我看到一些关于 URLSession.shared.dataTaskPublisher(\_: URL) 或一些 "delayed" 出版商的例子,当 s2 也获得价值时,但我仍然不清楚改变出版商的语义如何改变它的行为这样。

问题是您没有使用确实有所作为的管道。考虑这个示例(基于 Cocoa With Love 文章),其中第二个订阅者在发布者发布一段时间后上线:

let pub1 = Timer.publish(every: 1, on: .main, in: .default)
let c1 = pub1.connect()
let scan = Publishers.Scan(upstream: pub1, initialResult: 0) { (a, b) -> Int in
    a + 1
}
scan.sink { print("a:", [=10=]) }.store(in:&storage)
delay(3) {
    scan.sink { print("b:", [=10=]) }.store(in:&self.storage)
}

重点是,只有一个 scan 并且在延迟后另一个订阅者出现时它正在生成 1、2、3。该订户会得到什么?它会选择我们现在所在的位置吗?不,我们得到这个:

a: 1
a: 2
a: 3
a: 4
b: 1
a: 5
b: 2
a: 6
b: 3
...

所以实际上我们从第二次订阅重新开始,因为发布者是一个新副本。但是如果我们将发布者提升为class,我们会得到完全不同的结果:

let pub1 = Timer.publish(every: 1, on: .main, in: .default)
let c1 = pub1.connect()
let scan = Publishers.Scan(upstream: pub1, initialResult: 0) { (a, b) -> Int in
    a + 1
}
let scan2 = scan.share() // <--
scan2.sink { print("a:", [=12=]) }.store(in:&storage)
delay(3) {
    scan2.sink { print("b:", [=12=]) }.store(in:&self.storage)
}

现在我们得到这个:

a: 1
a: 2
a: 3
a: 4
b: 4
a: 5
b: 5
a: 6
b: 6
a: 7
b: 7

显然,这是一个非常显着的差异。如果您的发布者是主题,您会看到同样的事情,因为那是 class,而不是结构。

在我的选择中,如果你不添加.share(),将会有两个个事件流被两个人订阅订户。如果添加.share(),则只有一个个事件流被两个订阅者订阅。