这一小节中提到的数据合并本质上是多个publishers的合并,按照不同的合并方式可以分为3类:
- combineLatest
- merge
- zip
combineLatest
一图胜千言,上边这张图已经表明了combineLatest的最核心的思想:
- 它合并了2个publisher
- 输出的数据是成组的,数据结构为元组
数据成组即是优点也是缺点,优点就不多说了,缺点是,如果数据不能成组就不会输出数据。正常代码如下:
let firstPublisher = PassthroughSubject<Int, MyCustomError>()
let secondPublisher = PassthroughSubject<String, MyCustomError>()
firstPublisher
.combineLatest(secondPublisher)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
firstPublisher.send(1)
secondPublisher.send("a")
firstPublisher.send(2)
打印结果:
someValue: (1, "a")
someValue: (2, "a")
我们先让second publisher不输出数据看看输出是什么?
...
firstPublisher.send(1)
/// secondPublisher.send("a")
firstPublisher.send(2)
实际运行上边代码,并不会有任何输出,这就说明,如果2个publisher中,有任何一个没有数据,pipline就不会输出数据。
那么,如果second publisher在发送数据后,又发送了.finished事件会如何呢?我们稍微修改下代码:
...
firstPublisher.send(1)
secondPublisher.send("a")
secondPublisher.send(completion: Subscribers.Completion.finished)
firstPublisher.send(2)
打印结果:
someValue: (1, "a")
someValue: (2, "a")
可以看出,即使second publisher发送了.finished事件,整个pipline仍然会继续执行,在合并数据的时候,会使用publisher的最后一个数据。
更进一步,如果second publisher发送.failure事件会怎样呢?修改代码:
enum MyCustomError: Error {
case custom
}
...
firstPublisher.send(1)
secondPublisher.send("a")
secondPublisher.send(completion: Subscribers.Completion.failure(MyCustomError.custom))
firstPublisher.send(2)
someValue: (1, "a")
结束了
Error...
可以看出,pipline对error是十分敏感的,一旦发现错误,就立刻终止pipline。
大家明白了吗?总结一下:combineLatest的核心思想是把2个publisher的数据进行合并,正常情况下,2个publisher中任何一个产生新的数据,就会把数据合并后输出,遇到错误pipline立刻终止。
上边只讲解了combineLatest合并2个publisher,它还可以合并3个和4个publisher,用法都是一样的,看下边两张图片就明白了:
merge
merge也主要用于合并publisers,最多能够合并8个,最少2个,但是它有自己的特点,combineLatest会把每个publiser的latest数据组合成一个元组,而merge只要接受到新数据就输出。
merge比较适用于合并多个无序的publishers场景。至于某个publisher发送.finished或者.failure事件的逻辑跟combineLatest一样。
cancellables = Set<AnyCancellable>()
let pub1 = PassthroughSubject<Int, Never>()
let pub2 = PassthroughSubject<Int, Never>()
let pub3 = PassthroughSubject<Int, Never>()
let pub4 = PassthroughSubject<Int, Never>()
let pub5 = PassthroughSubject<Int, Never>()
let pub6 = PassthroughSubject<Int, Never>()
let pub7 = PassthroughSubject<Int, Never>()
let pub8 = PassthroughSubject<Int, Never>()
pub1
.merge(with: pub2, pub3,
pub4, pub5,
pub6, pub7, pub8)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
pub1.send(1)
pub2.send(2)
pub3.send(3)
pub4.send(4)
pub5.send(5)
pub6.send(6)
pub7.send(7)
pub8.send(8)
zip
zip和combineLatest非常相似,默认情况下会返回成组的数据,但他们最大的不同在于zip不会使用latest值,它使用的是新值。
观察上图,当pub2返回了数据4后,pipline中返回了(1, 4),说明了数据流中使用的数据是这2个publisher中从未使用过的新值,这些新值会遵循先入先出的规则。这就是zip最大的特点。
这个特点在某些场景下会非常有用,比方说需要等待2个异步请求完毕后,拿到2个数据继续执行其他的任务,这种需求就非常适合zip。
let pub1 = PassthroughSubject<Int, Never>()
let pub2 = PassthroughSubject<Int, Never>()
pub1
.zip(pub2)
.sink(receiveCompletion: { completion in
print("结束了")
switch completion {
case .finished:
print("完成")
case .failure(let error):
print("错误:\(error.localizedDescription)")
}
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
pub1.send(1)
pub1.send(2)
pub1.send(3)
pub2.send(4)
pub2.send(5)
zip最多可以合并4个publishers,原理都是相同的,我们在这里就略过不讲了,zip还有一个特性,它提供了一个闭包参数,允许我们对数据进行转换,我们先看看示意图:
可以看出,pipline的输出类型为String,我们可以使用闭包自由地对数据进行转换,这在平时的开发中非常有用。代码如下:
let numberPublisher = PassthroughSubject<Int, Never>()
let emojiPublisher = PassthroughSubject<String, Never>()
numberPublisher
.zip(emojiPublisher) { number, emoji -> String in
String(repeating: emoji, count: number)
}
.sink(receiveCompletion: { completion in
...
}, receiveValue: { someValue in
print("someValue: \(someValue)")
})
.store(in: &cancellables)
numberPublisher.send(2)
emojiPublisher.send("😂")
打印结果:
someValue: 😂😂
**总结一下,zip的特点是使用各个publisher的新值,而且能够进行数据映射。在真实开发中,当有多个异步任务需要合并的情景下可以考虑combineLatest, merge, zip。