结合 Publishers.Merge 但具有 Publishers.combineLatest 行为

Combine Publishers.Merge but with Publishers.combineLatest behaviour

我目前正在尝试实现两个发布者的合并。但是我找不到适合我的用例的解决方案。

我想合并 2 个都发出相同类型结构数组的发布者。我希望合并的发布者在合并的发布者之一发出新值时发出值。

基本上这将是 Publishers.CombineLatest 的一个用例,但由于我的底层发布者都发出相同类型的值,因此 merge 在这里更合适。但是 Publishers.Merge 不会记住合并发布者的最后值。

因此,我希望具有 Publishers.CombineLatest 行为和 Publishers.Merge 操作。 Combine 框架中是否有可以完成这种行为的东西?

大概会发生什么:

Definitions:

PublisherA: emits -> [Value]
PublisherB emits -> [Value]

CombinedAB: -> [Value]


PublisherA changes: CombinedAB -> [NewA, OldB]
PublisherB changes: CombinedAB -> [OldA, NewB]

let a = CurrentValueSubject<[Int], Never>(["a", "b", "c"])
let b = CurrentValueSubject<[Int], Never>(["d", "e", "f"])

let combined = Publisher.AnyThing(a, b)

combined.sink {
   print([=13=])
}


b.send(["g", "h", "i"])


Outputs:
["a", "b", "c", "d", "e", "f"]
["a", "b", "c", "g", "h", "i"]

所以它基本上是一个 Publishers.CombineLatest 但没有发出 (NewA,OldB) 的元组而是已经合并,因为两个值具有相同的类型。

非常感谢任何帮助。

假设您的合并操作只是您可以执行的子数组的连接:

let a = CurrentValueSubject<[String], Never>(["a", "b", "c"])
let b = CurrentValueSubject<[String], Never>(["d", "e", "f"])

let combined = Publishers.CombineLatest(a, b).map(+)

combined.sink {
   print([=10=])     //["a", "b", "c", "d", "e", "f"] and ["a", "b", "c", "g", "h", "i"]
}


b.send(["g", "h", "i"])

我不完全确定 "already merged" 是什么意思。 如果您希望最新发射的数组始终位于组合数组的末尾,那么您可能需要在 map(+) 之前使用 scan 运算符,以便能够与之前的发射进行比较并交换它们。

问题概述

docs 解释了三种组合发布者的方法之间的区别:

Use combineLatest(_:) when you want the downstream subscriber to receive a tuple of the most-recent element from multiple publishers when any of them emit a value. To pair elements from multiple publishers, use zip(_:) instead. To receive just the most-recent element from multiple publishers rather than tuples, use merge(with:).

ZipCombineLatest 是实时 一起 消费事件的唯一合适解决方案。在您的特定示例(发送事件的顺序)中,CombineLatest 是最佳解决方案,如下所述。

CombineLatest

你是对的,CombineLatest 确实监听来自嵌套发布者的事件,但是使用元组打印 Publisher 元素。这可以使用 mapcompactMap 轻松修复,具体取决于 Array 通用参数。 CombineLatest 一旦两个发布者都发布了一个元素,就只发布最新的未使用的事件。这意味着一旦两个发布者都发布了一个事件,那么所有个后续事件将被发布。

听力使用演示

let a = CurrentValueSubject<[String], Never>(["a", "b", "c"])
let b = CurrentValueSubject<[String], Never>(["d", "e", "f"])
a 
  .combineLatest(b, +)
  .sink { print("\([=10=])") }
  .store(in: &cancellableSet)
b.send(["g", "h", "i"])

// ["a", "b", "c", "d", "e", "f"]
// ["a", "b", "c", "g", "h", "i"]

Merge

Publishers.Merge 可能看起来更合适,因为相同的 Output 泛型类型,但事实并非如此。 Merge 只接收个人 Publisher 的最新发布元素。因此,即使我们已经“合并”了发布者,我们也无法打印合并的流。文档将 Merge 称为创建 interleaved stream 而不是组合的发布者。

合并听力使用演示:

let a = CurrentValueSubject<[String], Never>(["a", "b", "c"])
let b = CurrentValueSubject<[String], Never>(["d", "e", "f"])

let combined = Publishers.Merge(a, b)

combined.sink {
  print([=11=])
}
.store(in: &cancellableSet)

b.send(["g", "h", "i"])

// ["a", "b", "c"]
// ["d", "e", "f"]
// ["g", "h", "i"]

Zip

ZipCombineLatest 的可行替代方案,两者都是打印组合 Publisher 事件的有效选项。区别在于Zip在等待其他Publisher时发布最旧的未消费事件。

听力使用演示

let a = CurrentValueSubject<[String], Never>(["a", "b", "c"])
let b = CurrentValueSubject<[String], Never>(["d", "e", "f"])
a 
  .zip(b).map(+)
  .sink { print("\([=12=])") }
  .store(in: &cancellableSet)
b.send(["g", "h", "i"])

// ["a", "b", "c", "d", "e", "f"]

// To print "g","h","i", we need `a` to send an event.

a.send(["a", "b", "c"])
// ["a", "b", "c", "g", "h", "i"]

总结

使用 combineLatest 打印 组合 发布者事件。当两个 发布者必须同步 时,使用zip 打印组合 发布者事件。当您想 Publisher 个事件 单独 时使用 mergeMerge 适用于创建单个交错事件流。如果上游发布者成功完成或因错误而失败,zipped/combined/merged 发布者也会执行相同的操作。仔细考虑要为您的特定应用程序使用哪个运算符,不仅要通过类型签名,还要通过实际行为

奖金

如果您想组合超过 4 个 Publishers(为什么?),您实际上可以使用嵌套的 Publishers.CombineLatest4 和另一个 Publishers.CombineLatest。 SwiftUI 使用此技术将超过 10 个 SwiftUI View 组合在一个 ViewBuilder.

代码示例

let a = CurrentValueSubject<[String], Never>(["a", "b", "c"])
let b = CurrentValueSubject<[String], Never>(["d", "e", "f"])
let c = CurrentValueSubject<[String], Never>(["g", "h", "i"])
let d = CurrentValueSubject<[String], Never>(["j", "k", "l"])

let e = CurrentValueSubject<[String], Never>(["a", "b", "c"])
let f = CurrentValueSubject<[String], Never>(["d", "e", "f"])
let g = CurrentValueSubject<[String], Never>(["g", "h", "i"])
let h = CurrentValueSubject<[String], Never>(["j", "k", "l"])

let combinedOne = Publishers.CombineLatest4(a, b, c, d)
let combinedTwo = Publishers.CombineLatest4(e, f, g, h)
let combined = Publishers.CombineLatest(combinedOne, combinedTwo)