引子
Combine中虽然有众多内置Operators,但是要想实现对同一个Publisher多次订阅,且结果不丢失还是比较棘手的,除非实现自定义Publisher,但这不是今天我们的话题 😉
什么是多重订阅?
多重订阅就是多个订阅者订阅同一个Publisher。
什么是多重订阅不丢失?
不丢失指的是,在有多个订阅者的情况下,无论发生何种情况都可确保每个订阅者都可以接收到每一个消息。
即这是一个自动重发的机制,因为正常情况下Publisher源只会发布一次消息,绝不会为某个订阅者再发一次,你错过了只有怪你自己哦。
举个栗子
拿从网络读取Web数据这个异步操作来说,如果有订阅者在数据返回之后再订阅,它显然无法接收到之前发送的数据:
第一步: 创建发布者
第二步: 订阅者1订阅
第三步: 发布者返回Web数据
第四步: 订阅者1接收到数据
第五步: 订阅者2订阅(接收不到之前的数据)
如上所示,除非使用某种缓存再重发机制,订阅者2就会丢失之前的那次数据。
let url = URL(string: "https://www.csdn.net")!
let p = URLSession.shared.dataTaskPublisher(for: url)
.share()
.map(\.data)
var subscriptions = [AnyCancellable]()
// 模拟延时一段时间后再订阅
DispatchQueue.main.asyncAfter(deadline: .now()+5.0){
p.sink(receiveValue: {
// 订阅者2不会收到数据
print("2: \($0)")
}).store(in: &subscriptions)
}
// 订阅者1立即订阅
p.sink(receiveValue: {
// 订阅者1可以收到数据
print("1: \($0)")
}).store(in: &subscriptions)
上面这段简单的代码演示了多重订阅丢失的问题.
那么如何较简单的解决上述问题呢?
多播前来拯救
Combine中有一个multicast(多播)操作符,用它可以较好地满足上面的问题。
multicast操作符有一个重要的特性,按需手动刷新消息槽.
我们可以在需要时主动为所有订阅者发送消息,而不是让发布者“为所欲为”任性发送…
废话打住,上代码:
为创建的发布者链接multicast操作符:
let p = URLSession.shared.dataTaskPublisher(for: url)
.share()
.map(\.data)
.multicast {PassthroughSubject<Data,URLError>()}
注意其返回另一个Publisher,其中的泛型类型不能错!
现在运行代码,是神马结果???
结果就是不会有任何订阅者收到数据,上面说了multicast之后必须由你手动刷新消息槽。
接着在创建订阅者2的结尾加上一句:
// 手动刷新消息槽
p.connect()
.store(in: &subscriptions)
再次运行代码,你会发现两个订阅者都收到的消息,我们的目标就达到了 😉
总结
Combine中的操作符有很多,要想Combine用的好,用的妙,操作符必须烂熟于胸啊…
再会啦 😉