引子
Combine中雖然有衆多內置Operators,但是要想實現對同一個Publisher多次訂閱,且結果不丟失還是比較棘手的,除非實現自定義Publisher,但這不是今天我們的話題 😉
什麼是多重訂閱?
多重訂閱就是多個訂閱者訂閱同一個Publisher。
什麼是多重訂閱不丟失?
不丟失指的是,在有多個訂閱者的情況下,無論發生何種情況都可確保每個訂閱者都可以接收到每一個消息。
即這是一個自動重發的機制,因爲正常情況下Publisher源只會發佈一次消息,絕不會爲某個訂閱者再發一次,你錯過了只有怪你自己哦。
舉個栗子
拿從網絡讀取Web數據這個異步操作來說,如果有訂閱者在數據返回之後再訂閱,它顯然無法接收到之前發送的數據:
第一步: 創建發佈者
第二步: 訂閱者1訂閱
第三步: 發佈者返回Web數據
第四步: 訂閱者2接收到數據
第五步: 訂閱者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用的好,用的妙,操作符必須爛熟於胸啊…
再會啦 😉