SwiftUI(Combine)學(xué)習(xí)整理(三)

如果有RxSwift的學(xué)習(xí)經(jīng)驗(yàn)?zāi)敲蠢斫鈉ombine會(huì)更加迅速

通過(guò)對(duì)事件處理的操作進(jìn)行組合 (combine) ,來(lái)對(duì)異步事件進(jìn)行自定義處理 (這也正是 Combine 框架的名字的由來(lái))膏执。Combine 提供了一組聲明式的 Swift API悄谐,來(lái)處理隨時(shí)間變化的值。這些值可以代表用戶(hù)界面的事件矢空,網(wǎng)絡(luò)的響應(yīng)航罗,計(jì)劃好的事件,或者很多其他類(lèi)型的異步數(shù)據(jù)屁药。

在響應(yīng)式異步編程中粥血,一個(gè)事件及其對(duì)應(yīng)的數(shù)據(jù)被發(fā)布出來(lái),最后被訂閱者消化和使用。期間這些事件和數(shù)據(jù)需要通過(guò)一系列操作變形复亏,成為我們最終需要的事件和數(shù)據(jù)趾娃。Combine 中最重要的角色有三種,恰好對(duì)應(yīng)了這三種操作:負(fù)責(zé)發(fā)布事件的 Publisher缔御,負(fù)責(zé)訂閱事件的 Subscriber抬闷,以及負(fù)責(zé)轉(zhuǎn)換事件和數(shù)據(jù)的 Operator

image.png

image.png

Publisher

在 Combine 中,我們使用 Publisher 協(xié)議來(lái)代表事件的發(fā)布者刹淌。Swift 提倡使用面向協(xié)議編程的方式饶氏,Combine 中包括 Publisher 在內(nèi)的一系列角色都使用協(xié)議來(lái)進(jìn)行定義,也正是這一思想的具體體現(xiàn)有勾。協(xié)議本身定義了一個(gè) Publisher 應(yīng)該具備的能力疹启,而具體的實(shí)現(xiàn)則由具體的遵守 Publisher 協(xié)議的類(lèi)型提供。在 Combine 框架中蔼卡,已經(jīng)有一系列框架自帶的發(fā)布者類(lèi)型喊崖,它們大部分被定義在了 Publishers 這個(gè) enum 之中。我們會(huì)在下一章仔細(xì)研究一些常用的 Publisher 類(lèi)型的行為特點(diǎn)雇逞,在那之前荤懂,讓我們先來(lái)看一看 Publisher 共通的部分特性。

Publisher 協(xié)議中所必須的內(nèi)容十分簡(jiǎn)單塘砸,它包括兩個(gè)關(guān)聯(lián)類(lèi)型 (associatedtype) 以及一個(gè) receive 方法:

public protocol Publisher {
    associatedtype Output
    associatedtype Failure : Error
    func receive<S>(subscriber: S) where
          S : Subscriber,
                Self.Failure == S.Failure,
                Self.Output == S.Input
      }

Publisher 最主要的工作其實(shí)有兩個(gè):發(fā)布新的事件及其數(shù)據(jù)节仿,以及準(zhǔn)備好被 Subscriber訂閱。

Output 定義了某個(gè) Publisher 所發(fā)布的值的類(lèi)型掉蔬,F(xiàn)ailure 則定義可能產(chǎn)生的錯(cuò)誤的類(lèi)型廊宪。隨著時(shí)間的推移,事件流也會(huì)逐漸向前發(fā)展女轿。對(duì)應(yīng) Output 及 Failure箭启,Publisher 可以發(fā)布三種事件:
類(lèi)型為 Output 的新值:這代表事件流中出現(xiàn)了新的值。

類(lèi)型為 Failure 的錯(cuò)誤:這代表事件流中發(fā)生了問(wèn)題蛉迹,事件流到此終止傅寡。

完成事件:表示事件流中所有的元素都已經(jīng)發(fā)布結(jié)束,事件流到此終止北救。
對(duì)于第一種事件荐操,Publisher 會(huì)直接將新的值發(fā)布出來(lái)。后兩種事件在 Combine 中 則使用Subscribers.Completion 來(lái)描述珍策,它是一個(gè)含有兩個(gè)成員的 enum托启,其中成 員類(lèi)型為 .failure(Failure) 以及 .finished。我們?cè)诒緯?shū)后面的部分膛壹,會(huì)使用 output驾中, failure 和 finished 來(lái)描述這三種事件唉堪。

雖然 Publisher 可以發(fā)布三種事件,但是它們并不是必須的肩民。一個(gè) Publisher 可能發(fā) 出一個(gè)或多個(gè) output 值唠亚,也可能一個(gè)值都不發(fā)出;Publisher 有可能永遠(yuǎn)不會(huì)停止 終結(jié),也有可能通過(guò) failure 或者 finished 事件來(lái)表明不再會(huì)發(fā)出新的事件持痰。我們將 最終會(huì)終結(jié)的事件流稱(chēng)為有限事件流灶搜,而將不會(huì)發(fā)出 failure 或者 finished 的事件流 稱(chēng)為無(wú)限事件流。

有限事件流和無(wú)限事件流
有限事件流最常見(jiàn)的一個(gè)例子是網(wǎng)絡(luò)請(qǐng)求的相關(guān)操作:發(fā)出網(wǎng)絡(luò)請(qǐng)求后工窍,可以把每 次接收到數(shù)據(jù)的事件看作一個(gè)output割卖。在請(qǐng)求的所有數(shù)據(jù)都被返回時(shí),整個(gè)操作正 常結(jié)束患雏,finished 事件被發(fā)布鹏溯。如果在過(guò)程中遭遇到錯(cuò)誤,比如網(wǎng)絡(luò)連接斷開(kāi)或者連 接被服務(wù)器關(guān)閉等淹仑,則發(fā)布 failure 事件丙挽。不論是 finished 或者是 failure,都表明這 次請(qǐng)求已經(jīng)完成匀借,將來(lái)不會(huì)有更多的 output 發(fā)生颜阐。
無(wú)限事件流則正好相反,這類(lèi) Publisher 永遠(yuǎn)不會(huì)發(fā)出 failure 或者 finished吓肋。一 個(gè)典型的例子是 UI 操作凳怨,比如用戶(hù)點(diǎn)擊某個(gè)按鈕的事件流:如果將按鈕看作是事件 的發(fā)布者,每次按鈕點(diǎn)擊將發(fā)布一個(gè)不帶有任何數(shù)據(jù)的 output是鬼。這種按鈕操作并沒(méi) 有嚴(yán)格意義上的完結(jié):用戶(hù)可能一次都沒(méi)有點(diǎn)擊這個(gè)按鈕肤舞,也可能無(wú)限次地點(diǎn)擊這 個(gè)按鈕,不論用戶(hù)如何操作屑咳,你都無(wú)法斷言之后不會(huì)再發(fā)生任何按鈕事件萨赁。

Operator

客戶(hù)端的響應(yīng)式編程中弊琴,由狀態(tài)驅(qū)動(dòng) UI 是最核心的思想兆龙。不過(guò),異步 API 的 Publisher 所提供的事件和數(shù)據(jù)敲董,往往并不能夠直接用來(lái)驅(qū)動(dòng)決定 UI 的狀態(tài)紫皇。

在響應(yīng)式編程中,絕大部分的邏輯和關(guān)鍵代碼的編寫(xiě)腋寨,都發(fā)生在數(shù)據(jù)處理和變形中聪铺。 每個(gè) Operator 的行為模式都一樣:它們使用上游 Publisher 所發(fā)布的數(shù)據(jù)作為輸入, 以此產(chǎn)生的新的數(shù)據(jù)萄窜,然后自身成為新的 Publisher铃剔,并將這些新的數(shù)據(jù)作為輸出撒桨, 發(fā)布給下游。通過(guò)一系列組合键兜,我們可以得到一個(gè)響應(yīng)式的 Publisher 鏈條:當(dāng)鏈條 最上端的 Publisher 發(fā)布某個(gè)事件后凤类,鏈條中的各個(gè) Operator 對(duì)事件和數(shù)據(jù)進(jìn)行處 理。在鏈條的末端我們希望最終能得到可以直接驅(qū)動(dòng) UI 狀態(tài)的事件和數(shù)據(jù)普气。這樣谜疤, 終端的消費(fèi)者可以直接使用這些準(zhǔn)備好的數(shù)據(jù),而這個(gè)消費(fèi)者的角色由 Subscriber 來(lái)?yè)?dān)任现诀。

Subscriber

和 Publisher 類(lèi)似夷磕,Combine 中的 Subscriber 也是一個(gè)抽象的協(xié)議,它定義了某個(gè) 類(lèi)型想要成為訂閱者角色時(shí)所需要滿(mǎn)足的條件:

public protocol Subscriber { associatedtype Input associatedtype Failure : Error
func receive(subscription: Subscription)
func receive(_ input: Self.Input) !" Subscribers.Demand
func receive(completion: Subscribers.Completion<Self.Failure>)
}

定義中 Input 和 Failure 分別表示了訂閱者能夠接受的事件流數(shù)據(jù)類(lèi)型和錯(cuò)誤類(lèi)型仔沿。 想要訂閱某個(gè) Publisher坐桩,Subscriber 中的這兩個(gè)類(lèi)型必須與 Publisher 的 Output 和 Failure 相同。
Combine 中也定義了幾個(gè)比較常見(jiàn)的 Subscriber封锉,我們承接上面的按鈕的例子來(lái)進(jìn) 行說(shuō)明撕攒。在上面,我們通過(guò) scan 和 map烘浦,對(duì) buttonClicked 進(jìn)行了變形抖坪,將它從一 個(gè)不含數(shù)據(jù)的按鈕事件流,轉(zhuǎn)變?yōu)榱艘?String 表示的按鈕按下次數(shù)的計(jì)數(shù)闷叉。如果我 們想要訂閱和使用這些值擦俐,可以使用 sink:

let buttonClicked: AnyPublisher<Void, Never> buttonClicked
.scan(0) { value, _ in value + 1 }
.map { String($0) }
.sink { print("Button pressed count: \($0)") }

因?yàn)?buttonClicked 是一個(gè)無(wú)限事件流,所以我們?cè)谏厦嬷粚?duì) output 值進(jìn)行了打印握侧。 sink 方法完整的函數(shù)簽名如下:

func sink( receiveCompletion:
@escaping ((Subscribers.Completion<Self.Failure>) !" Void), receiveValue:
@escaping ((Self.Output) !" Void) ) !" AnyCancellable

你可以同時(shí)提供兩個(gè)閉包蚯瞧,receiveCompletion 用來(lái)接收 failure 或者 finished 事件, receiveValue 用來(lái)接收 output 值品擎。
sink 可以充當(dāng)一座橋梁埋合,將響應(yīng)函數(shù)式的 Publisher 鏈?zhǔn)酱a,終結(jié)并橋接到基于 閉包的指令式世界中來(lái)萄传。如果你不得不在指令式的世界中進(jìn)行一些操作甚颂,或者只是 以學(xué)習(xí)和驗(yàn)證為目的,那么使用 sink 無(wú)可厚非秀菱。但是如果你是想要讓數(shù)據(jù)繼續(xù)在 SwiftUI 的聲明式的世界中來(lái)驅(qū)動(dòng) UI 的話(huà)振诬,另一個(gè) Subscriber 可能會(huì)更為簡(jiǎn)潔常用, 那就是 assign衍菱。

和通過(guò) sink 提供閉包赶么,可以執(zhí)行任意操作不同,assign 接受一個(gè) class 對(duì)象以及對(duì) 象類(lèi)型上的某個(gè)鍵路徑 (key path)脊串。每當(dāng) output 事件到來(lái)時(shí)辫呻,其中包含的值就將被 設(shè)置到對(duì)應(yīng)的屬性上去:

class Foo {
var bar: String = ""
}
let foo = Foo()
let buttonClicked: AnyPublisher<Void, Never> buttonClicked
.scan(0) { value, _ in value + 1 } .map { String($0) }
.assign(to: \.bar, on: foo).

這樣的 Subscriber 讓我們可以徹底擺脫指令式的寫(xiě)法清钥,直接將事件值 “綁定” 到具 體的屬性上。assign 方法的具體定義如下放闺,它要求 keyPath 滿(mǎn)足 ReferenceWritableKeyPath:

func assign<Root>(
to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root
) !" AnyCancellable

也就是說(shuō)循捺,只有那些 class 類(lèi)型的實(shí)例中的屬性能被綁定。在 SwiftUI 中雄人,代表 View 對(duì)應(yīng)的模型的 ObservableObject 接口只能由 class 修飾的類(lèi)型來(lái)實(shí)現(xiàn)从橘,這也 正是 assign 最常用的地方。

其他角色

Publisher础钠,Operator 和 Subscriber 三者組成了從事件發(fā)布恰力,變形,到訂閱的完整 鏈條旗吁。在建立起事件流的響應(yīng)鏈后踩萎,隨著事件發(fā)生,app 的狀態(tài)隨之演變很钓,這些是響 應(yīng)式編程處理異步程序的 Combine 框架的基礎(chǔ)架構(gòu)香府。
除此之外,Combine 框架中還有兩個(gè)比較重要的概念码倦,那就是 SubjectScheduler企孩。它們和 Publisher 及 Subscriber 一樣,都是通過(guò) protocol 的方式來(lái)對(duì) 抽象概念進(jìn)行描述袁稽。

Subject

Subject 本身也是一個(gè) Publisher:

public protocol Subject : AnyObject, Publisher {
func send(_ value: Self.Output)
func send(completion: Subscribers.Completion<Self.Failure>)
}

從定義可以看到勿璃,Subject 暴露了兩個(gè) send 方法,外部調(diào)用者可以通過(guò)這兩個(gè)方法 來(lái)主動(dòng)地發(fā)布 output 值推汽、failure 事件或 finished 事件补疑。如果我們說(shuō) sink 提供了由 函數(shù)響應(yīng)式向指令式編程轉(zhuǎn)變的路徑的話(huà),Subject 則補(bǔ)全了這條通路的另一側(cè):它 讓你可以將傳統(tǒng)的指令式異步 API 里的事件和信號(hào)轉(zhuǎn)換到響應(yīng)式的世界中去歹撒。
Combine 內(nèi)置提供了兩種常用的 Subject 類(lèi)型莲组,分別是 PassthroughSubjectCurrentValueSubject

PassthroughSubject 簡(jiǎn)單地將 send 接收到的事件轉(zhuǎn)發(fā)給 下游的其他 Publisher 或 Subscriber,PassthroughSubject 并不會(huì)對(duì)接受到的值進(jìn)行保留暖夭,當(dāng)訂閱開(kāi)始后锹杈,它將監(jiān)聽(tīng)并響 應(yīng)接下來(lái)的事件。
CurrentValueSubject 則會(huì)包裝和持有一個(gè)值鳞尔,并在 設(shè)置該值時(shí)發(fā)送事件并保留新的值嬉橙。在訂閱發(fā)生的瞬間早直,CurrentValueSubject 會(huì)把 當(dāng)前保存的值發(fā)送給訂閱者寥假。

Scheduler

如果說(shuō) Publisher 決定了發(fā)布怎樣的 (what) 事件流的話(huà),Scheduler 所要解決的就 是兩個(gè)問(wèn)題:在什么地方 (where)霞扬,以及在什么時(shí)候 (when) 來(lái)發(fā)布事件和執(zhí)行代碼糕韧。

關(guān)于 where

在更新 UI 時(shí)枫振,我們需要保證相關(guān)操作發(fā)生在主線(xiàn)程。因?yàn)楫惒?API 往往涉及到在不 同線(xiàn)程間切換萤彩,這個(gè)問(wèn)題就顯得尤為重要粪滤。比如,使用 URLSession 進(jìn)行網(wǎng)絡(luò)請(qǐng)求雀扶, 默認(rèn)情況下異步回調(diào)方法會(huì)在后臺(tái)線(xiàn)程被調(diào)用杖小,如果這時(shí)候需要根據(jù)數(shù)據(jù)更新 UI, 最簡(jiǎn)單的方式就是將它

Dispatch 到 main queue 中去:
URLSession.shared.dataTask(
with: URL(string: "https:!"example.com")!)
{
data, _, _ in
if let data = data,
let text = String(data: data, encoding: .utf8) {
DispatchQueue.main.async {
!" 在 main queue 中執(zhí)行 UI 更新
textView.text = text }
} }.resume()

異步的響應(yīng)式編程中也一樣愚墓,如果前序的 Publisher 是在后臺(tái)線(xiàn)程進(jìn)行操作予权,那么在 訂閱時(shí),當(dāng)狀態(tài)的變化會(huì)影響 UI 時(shí)浪册,我們需要將接收事件的線(xiàn)程切換到主線(xiàn)程扫腺。 Combine 里提供了 receive(on:options:) 來(lái)讓下游在指定的線(xiàn)程中接收事件。比如村象, 對(duì)于后臺(tái)線(xiàn)程的網(wǎng)絡(luò)請(qǐng)求返回笆环,可以通過(guò)這樣的方式在 main runloop 中進(jìn)行處理:

URLSession.shared
.dataTaskPublisher(for: URL(string: "https:!"example.com")!) .compactMap { String(data: $0.data, encoding: .utf8) } .receive(on: RunLoop.main)
.sink(receiveCompletion: { _ in
}, receiveValue: { textView.text = $0
})

RunLoop 就是一個(gè)實(shí)現(xiàn)了 Scheduler 協(xié)議的類(lèi)型,它知道要如何執(zhí)行后續(xù)的訂閱任 務(wù)厚者。如果沒(méi)有 receive(on: RunLoop.main) 的話(huà)躁劣,sink 的閉包將會(huì)在后臺(tái)線(xiàn)程執(zhí)行, 這在更新 UI 時(shí)將帶來(lái)問(wèn)題库菲。

關(guān)于 when

Scheduler 的另一個(gè)重要工作是為事件的發(fā)布指定和規(guī)劃時(shí)間习绢。默認(rèn)情況下,被訂閱 的 Publisher 將盡可能快地把事件傳遞給后續(xù)的處理流程蝙昙,不過(guò)有些情況下闪萄,我們會(huì) 希望改變事件鏈的傳遞時(shí)間,比如加入延遲或者等待空閑時(shí)再進(jìn)行傳遞奇颠,這些延時(shí) 也是由 Scheduler 負(fù)責(zé)調(diào)度的败去。
比較常見(jiàn)的兩種操作是 delay 和 debounce。delay 簡(jiǎn)單地將所有事件按照一定事件 延后烈拒。debounce則是設(shè)置了一個(gè)計(jì)時(shí)器圆裕,在事件第一次到來(lái)時(shí),計(jì)時(shí)器啟動(dòng)荆几。在計(jì)時(shí)器有效期間吓妆,每次接收到新值,則將計(jì)時(shí)器時(shí)間重置吨铸。當(dāng)且僅當(dāng)計(jì)時(shí)窗口中沒(méi)有新的值到來(lái)時(shí)腥光,最后一次事件的值才會(huì)被當(dāng)作新的事件發(fā)送出去。

Publisher 和常見(jiàn)Operator

某些例如map和flatmap等常見(jiàn)的高階函數(shù)不在總結(jié)

Publisher 和 Subscriber 的調(diào)用關(guān)系如下:

Subscriber.png

基礎(chǔ) Publisher 和 Operator

元素變形

reduce 和 scan

reduce 方法可以將數(shù)組中的元素按照某種規(guī)則進(jìn)行合并聂喇,并得到一個(gè)最終的結(jié)果
我們也有可能會(huì)想要把中途的過(guò)程保存下來(lái)。在 Array 中竭缝,這種操作一般叫做 scan

map

compactMap 比較簡(jiǎn)單,它的作用是將 map 結(jié)果中那些 nil 的元素去除掉沼瘫,這個(gè)操作通常會(huì) “壓縮” 結(jié)果抬纸,讓其中的元素?cái)?shù)減少
flatMap 則要復(fù)雜許多。map 及 compactMap 的閉包返回值是單個(gè)的 Output 值0耿戚。

check("Compact Map") {
["1", "2", "3", "cat", "5"]
.publisher
.compactMap { Int($0) } }
!" 輸出:
!" ----- Compact Map -----
!" receive subscription: ([1, 2, 3, 5]) !" request unlimited
!" receive value: (1)
!" receive value: (2)
!" receive value: (3) !" receive value: (5) !" receive finished

等價(jià)于

check("Compact Map By Filter") { ["1", "2", "3", "cat", "5"]
.publisher
.map { Int($0) } .filter { $0 !" nil } .map { $0! }
}

而與它們不同湿故,flatMap 的變形閉包里需要返回 一個(gè) Publisher。也就是說(shuō)膜蛔,flatMap 將會(huì)涉及兩個(gè) Publisher:一個(gè)是
flatMap 操 作本身所作用的外層 Publisher晓锻,一個(gè)是 flatMap 所接受的變形閉包中返回的內(nèi)層 Publisher。flatMap 將外層 Publisher 發(fā)出的事件中的值傳遞給內(nèi)層 Publisher飞几,然 后匯總內(nèi)層 Publisher 給出的事件輸出砚哆,作為最終變形后的結(jié)果

removeDuplicates

Publisher 還有一個(gè)很方便的操作, removeDuplicates屑墨。在一個(gè)不斷發(fā)送 Output 值的事件流中躁锁,可能會(huì)存在連續(xù)多次 發(fā)送相同事件的情況。有時(shí)卵史,我們會(huì)想要過(guò)濾掉這些重復(fù)的事件战转,進(jìn)而避免無(wú)謂的額 外開(kāi)銷(xiāo)。移除連續(xù)出現(xiàn)的重復(fù)事件值以躯,正是 removeDuplicates 所提供的

check("Remove Duplicates") {
["S", "Sw", "Sw", "Sw", "Swi", "Swif", "Swift", "Swift", "Swif"]
.publisher
.removeDuplicates() }
!" 輸出:
!" ----- Remove Duplicates -----
!" receive subscription: (["S", "Sw", "Swi", "Swif", "Swift", "Swif"]) !" request unlimited
!" receive value: (S)
!" receive value: (Sw)
!" receive value: (Swi)
!" receive value: (Swif)
!" receive value: (Swift)

錯(cuò)誤處理

Publisher 總是在發(fā)布一些值后槐秧,以 .finished 事件作為 正常結(jié)束。但實(shí)際上忧设,Publisher 的結(jié)束事件有兩種可能:代表正常完成的 .finished 和代表發(fā)生了某個(gè)錯(cuò)誤的 .failure刁标,兩者都表示 Publisher 不再會(huì)有新的事件發(fā)出
Subscriber 在訂閱上游 Publisher 時(shí),不僅需要保證 Publisher.Output 的類(lèi)型和 Subscriber.Input 的類(lèi)型一致址晕,也要保證兩者所接受的 Failure 也具有相同類(lèi)型

page201image48190752.png

如果 Publisher 在出錯(cuò)時(shí)發(fā)送的是 SampleError膀懈,但訂閱方聲明只接受 MyError 時(shí), 就算實(shí)際上 Publisher 只發(fā)出 Output 值而從不會(huì)發(fā)出 Failure 值谨垃,我們也無(wú)法使用 這個(gè) Subscriber 去接收一個(gè)類(lèi)型不符的 Publisher 的事件启搂。
如果 Publisher 在出錯(cuò)時(shí)發(fā)送的是 SampleError,但訂閱方聲明只接受 MyError 時(shí)刘陶, 就算實(shí)際上 Publisher 只發(fā)出 Output 值而從不會(huì)發(fā)出 Failure 值胳赌,我們也無(wú)法使用 這個(gè) Subscriber 去接收一個(gè)類(lèi)型不符的 Publisher 的事件。
在這種情況下匙隔,我們可以通過(guò)使用 mapError 來(lái)將 Publisher 的 Failure 轉(zhuǎn)換成 Subscriber 所需要的 Failure 類(lèi)型:

拋出錯(cuò)誤

有時(shí)候疑苫,Operator 在對(duì)上游 Output 的數(shù)據(jù)進(jìn)行處理時(shí),可能會(huì)遇到發(fā)生錯(cuò)誤的情 況。比如說(shuō)嘗試將一個(gè)字符串轉(zhuǎn)換為 Int 時(shí)缀匕,我們并不總是能得到結(jié)果 Int纳决。有時(shí)候 我們可以用上面提到的 compactMap 來(lái)把這種結(jié)果過(guò)濾掉碰逸,但是有些時(shí)候乡小,我們會(huì) 希望不要放過(guò)這種 “例外”,而是讓事件流以明確的錯(cuò)誤作為結(jié)束饵史,來(lái)表明輸入數(shù)據(jù) 出現(xiàn)了問(wèn)題满钟。

Combine 為 Publisher 的 map 操作提供了一個(gè)可以?huà)伋鲥e(cuò)誤的版本,tryMap胳喷。使用 tryMap 我們就可以將這類(lèi)處理數(shù)據(jù)時(shí)發(fā)生的錯(cuò)誤轉(zhuǎn)變?yōu)闃?biāo)志事件流失敗的結(jié)束事件:

check("Throw") {
["1", "2", "Swift", "4"].publisher
.tryMap { s !" Int in
guard let value = Int(s) else {
throw MyError.myError }
return value }
}
!" 輸出:
!" ----- Throw -----
!" receive subscription: (TryMap) !" request unlimited
!" receive value: (1)
!" receive value: (2)
!" receive error: (myError)

除了 tryMap 以外湃番,Combine 中還有很多類(lèi)似的以 try 開(kāi)頭的 Operator,比如 tryScan吭露,tryFilter吠撮,tryReduce 等等。當(dāng)你有需求在數(shù)據(jù)轉(zhuǎn)換或者處理時(shí)讲竿,將事件 流以錯(cuò)誤進(jìn)行終止泥兰,都可以使用對(duì)應(yīng)操作的 try 版本來(lái)進(jìn)行拋出,并在訂閱者一側(cè)接 收到對(duì)應(yīng)的錯(cuò)誤事件题禀。

從錯(cuò)誤中恢復(fù)

不管是什么語(yǔ)言或者框架鞋诗,錯(cuò)誤處理總是很考驗(yàn)細(xì)節(jié)的地方。大多數(shù)情況下我們可 能會(huì)以某種形式把錯(cuò)誤反饋給用戶(hù)迈嘹,用彈框或者文本告訴他們某個(gè)地方可能出了問(wèn) 題削彬。不過(guò)有些時(shí)候,我們也可能會(huì)選擇使用默認(rèn)值來(lái)讓事件流從錯(cuò)誤中 “恢復(fù)”秀仲。
在 Combine 里融痛,有一些 Operator 是專(zhuān)門(mén)幫助事件流從錯(cuò)誤中恢復(fù)的,最簡(jiǎn)單的是 replaceError神僵,它會(huì)把錯(cuò)誤替換成一個(gè)給定的值酌心,并且立即發(fā)送 finished 事件:

check("Replace Error") {
["1", "2", "Swift", "4"].publisher
.tryMap { s !" Int in
guard let value = Int(s) else {
throw MyError.myError }
return value }
.replaceError(with: -1) }

!" 輸出:
!" ----- Replace Error -----
!" receive subscription: (ReplaceError) !" request unlimited
!" receive value: (1)
!" receive value: (2)
!" receive value: (-1)
!" receive finished

如果我們想要在事件流以錯(cuò)誤結(jié)束時(shí)被轉(zhuǎn)為一個(gè)默認(rèn)值的話(huà),replaceError 就會(huì)很 有用挑豌。replaceError 會(huì)將 Publisher 的 Failure 類(lèi)型抹為 Never安券,這正是我們使用 assign 來(lái)將 Publisher 綁定到 UI 上時(shí)所需要的 Failure 類(lèi)型。我們可以用 replaceError 來(lái)提供這樣一個(gè)在出現(xiàn)錯(cuò)誤時(shí)應(yīng)該顯示的默認(rèn)值氓英。

page205image47366144.png

replaceError 在錯(cuò)誤時(shí)接受單個(gè)值侯勉,另一個(gè)操作 catch 則略有不同,它接受的是一 個(gè)新的 Publisher铝阐,當(dāng)上游 Publisher 發(fā)生錯(cuò)誤時(shí)址貌,catch 操作會(huì)使用新的 Publisher 來(lái)把原來(lái)的 Publisher 替換掉。舉個(gè)例子:

check("Catch with Just") {
["1", "2", "Swift", "4"].publisher
.tryMap { s !" Int in
guard let value = Int(s) else {
throw MyError.myError }
return value }
.catch { _ in Just(-1) } }
!" ----- Catch with Just ----- !" receive subscription: (Catch) !" request unlimited
!" receive value: (1)
!" receive value: (2)
!" receive value: (-1)
!" receive finished

看上去輸出和上面的 replaceError 沒(méi)有區(qū)別,但是記住在 catch 的閉包中练对,我們返 回的是 Just(-1) 這個(gè) Publisher遍蟋,而不僅僅只是 Int 的 -1。實(shí)際上螟凭,任何滿(mǎn)足 Output == Int 和 Failure == Never 的 Publisher 都可以作為 catch 的閉包被返回虚青, 并替代原來(lái)的 Publisher:


Publisher.png

總結(jié)

在學(xué)習(xí) Combine 框架時(shí),只有確實(shí)理解了每個(gè) Operator 的作用和行為特點(diǎn)螺男,才能 進(jìn)一步理解各種 Operator 在組合后所形成的邏輯棒厘。最終,依靠這些小塊知識(shí)和常見(jiàn) 模式下隧,才能按照需求寫(xiě)出合適的組合邏輯奢人。毫無(wú)疑問(wèn),如果沒(méi)有各個(gè) Operator 的知 識(shí)基石淆院,是不可能構(gòu)建出一套異步響應(yīng)式的邏輯大廈的何乎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市土辩,隨后出現(xiàn)的幾起案子支救,更是在濱河造成了極大的恐慌,老刑警劉巖脯燃,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搂妻,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡辕棚,警方通過(guò)查閱死者的電腦和手機(jī)欲主,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)逝嚎,“玉大人扁瓢,你說(shuō)我怎么就攤上這事〔咕” “怎么了引几?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)挽铁。 經(jīng)常有香客問(wèn)我伟桅,道長(zhǎng),這世上最難降的妖魔是什么叽掘? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任楣铁,我火速辦了婚禮,結(jié)果婚禮上更扁,老公的妹妹穿的比我還像新娘盖腕。我一直安慰自己赫冬,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布溃列。 她就那樣靜靜地躺著劲厌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪听隐。 梳的紋絲不亂的頭發(fā)上补鼻,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音遵绰,去河邊找鬼辽幌。 笑死增淹,一個(gè)胖子當(dāng)著我的面吹牛椿访,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虑润,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼成玫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拳喻?” 一聲冷哼從身側(cè)響起哭当,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冗澈,沒(méi)想到半個(gè)月后钦勘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡亚亲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年彻采,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捌归。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肛响,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惜索,到底是詐尸還是另有隱情特笋,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布巾兆,位于F島的核電站猎物,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏角塑。R本人自食惡果不足惜蔫磨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吉拳。 院中可真熱鬧质帅,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至魄揉,卻和暖如春剪侮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背洛退。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工瓣俯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人兵怯。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓彩匕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親媒区。 傳聞我的和親對(duì)象是個(gè)殘疾皇子驼仪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355