上一篇講解了 Combine 中的兩大概念:Publisher 和 Subscriber 以及其基本使用。這一篇主要講解一下缩功,當 Combine 流中發(fā)生錯誤,我們應該怎么處理。
錯誤主要是分為兩大類沛贪,一種是錯誤類型不匹配怕午,一種則是上下游操作發(fā)生異常废登。
錯誤類型不匹配
假設,我們要實現(xiàn)一個加載網(wǎng)絡圖片的一個需求郁惜。
首先鄙煤,定義一個 RequestError
的枚舉,用來表示請求過程中發(fā)生的錯誤:
enum RequestError: Error {
case sessionError(error: Error)
}
接著圃庭,聲明兩個實例對象:
private let imageURLPublisher = PassthroughSubject<URL, RequestError>() // 圖片加載請求發(fā)布者
private var cancel: AnyCancellable?
然后蝙眶,將數(shù)據(jù)與接受者進行綁定:
cancel = imageURLPublisher.flatMap { requestURL in
return URLSession.shared.dataTaskPublisher(for: requestURL)
}.sink { error in
print(error)
} receiveValue: { result in
let image = UIImage(data: result.data)
print(image)
}
但此時你會發(fā)現(xiàn)編譯報錯:原因在于 DataTaskPublisher
的錯誤類型為 URLError
, 與 RequestError
類型是不一致恨樟。 DataTaskPublisher 源碼:
public struct DataTaskPublisher : Publisher, Sendable {
public typealias Output = (data: Data, response: URLResponse)
public typealias Failure = URLError
public let request: URLRequest
public let session: URLSession
public init(request: URLRequest, session: URLSession)
public func receive<S>(subscriber: S) where S : Subscriber, S.Failure == URLError, S.Input == (data: Data, response: URLResponse)
}
我們用 mapError 將錯誤類型進行一下轉(zhuǎn)換就可以了:
cancel = imageURLPublisher.flatMap { requestURL in
return URLSession.shared.dataTaskPublisher(for: requestURL)
.mapError { error -> RequestError in
return RequestError.sessionError(error: error)
}
}.sink { error in
print(error)
} receiveValue: { result in
let image = UIImage(data: result.data)
print(image)
}
最后半醉,就是在 touchesBegan 中觸發(fā)圖片加載:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
imageURLPublisher.send(URL(string: "https://httpbin.org/image/jpeg")!)
// RequestError.sessionError(error: Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made."
imageURLPublisher.send(URL(string: "https://unknown.url/image")!) // 這個會走 Error 的分支,打印上述錯誤信息
}
運行一下項目劝术,點擊一下屏幕就可以看到控制臺對 image 的打印了缩多。
上下游操作發(fā)生異常
當操作流發(fā)生異常的時候,比如:網(wǎng)絡加載失敗养晋、JSON 字符串解析失敗等衬吆。我們可以通過下述的幾個函數(shù)來進行錯誤處理。
assertNoFailure(_:file:line:)
當上游發(fā)布者發(fā)生錯誤時绳泉,拋出異常逊抡。主要用在測試版本中,用來提前發(fā)現(xiàn)某些場景下的錯誤零酪。
代碼示例:
public enum SubjectError: Error {
case genericSubjectError
}
let subject = PassthroughSubject<String, Error>()
cancel = subject
.assertNoFailure()
.sink(receiveCompletion: { print ("completion: \($0)") },
receiveValue: { print ("value: \($0).") }
)
subject.send("數(shù)據(jù)")
subject.send(completion: Subscribers.Completion<Error>.failure(SubjectError.genericSubjectError))
當 subject 第一次調(diào)用 send 會發(fā)送一個 "數(shù)據(jù)" 的字符串冒嫡,第二次調(diào)用則是發(fā)送了一個 SubjectError
類型的錯誤。
因為 subject 調(diào)用了 assertNoFailure()
函數(shù)四苇,所以孝凌,當?shù)诙握{(diào)用 send 的時候會直接拋出異常。
catch 和 replaceError
當在 Combine 流中發(fā)生錯誤時月腋,如果你想捕獲錯誤并且忽略它蟀架,可以使用 catch
操作符。該操作符可以在發(fā)生錯誤的時候榆骚,允許你返回一個默認值片拍。比如下面這兩種場景:
- 當搜索結(jié)果發(fā)生錯誤時,返回一個空數(shù)組妓肢。
- 當加載網(wǎng)絡圖片失敗時捌省,返回一個占位圖。 以下是加載網(wǎng)絡圖片失敗的示例代碼:
cancel = imageURLPublisher.flatMap { requestURL in
...與上文一致
}.map({ result -> UIImage? in
return UIImage(data: result.data)
}).catch({ error -> Just<UIImage?> in
return Just(notFoundImage)
}).sink(receiveValue: { image in
print(image)
})
當 Combine 發(fā)生錯誤的時候职恳,也可以使用 replaceError(with:)
來進行處理所禀。它的作用是將錯誤替換成提供的值方面。當你想要通過發(fā)送單個替換元素來處理錯誤并結(jié)束流時是非常有用的。
它和 catch 的區(qū)別就是:完全忽略錯誤色徘,不會去關心錯誤信息恭金。而 catch 我們是可以根據(jù)錯誤信息來進行邏輯處理的。它的使用就是將上述的 catch 替換為 .replaceError(with: notFoundImage)
即可褂策。