结合 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:)
.
Zip
和 CombineLatest
是实时 一起 消费事件的唯一合适解决方案。在您的特定示例(发送事件的顺序)中,CombineLatest
是最佳解决方案,如下所述。
CombineLatest
你是对的,CombineLatest
确实监听来自嵌套发布者的事件,但是使用元组打印 Publisher
元素。这可以使用 map
或 compactMap
轻松修复,具体取决于 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
Zip
是 CombineLatest
的可行替代方案,两者都是打印组合 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
个事件 单独 时使用 merge
。 Merge
适用于创建单个交错事件流。如果上游发布者成功完成或因错误而失败,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)
我目前正在尝试实现两个发布者的合并。但是我找不到适合我的用例的解决方案。
我想合并 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, usezip(_:)
instead. To receive just the most-recent element from multiple publishers rather than tuples, usemerge(with:)
.
Zip
和 CombineLatest
是实时 一起 消费事件的唯一合适解决方案。在您的特定示例(发送事件的顺序)中,CombineLatest
是最佳解决方案,如下所述。
CombineLatest
你是对的,CombineLatest
确实监听来自嵌套发布者的事件,但是使用元组打印 Publisher
元素。这可以使用 map
或 compactMap
轻松修复,具体取决于 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
Zip
是 CombineLatest
的可行替代方案,两者都是打印组合 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
个事件 单独 时使用 merge
。 Merge
适用于创建单个交错事件流。如果上游发布者成功完成或因错误而失败,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)