我想分享一種技術,在使用Swift do, try, catch error處理模型時,我發現它非常有用,可以限制從給定API呼叫中丟擲的錯誤數量。
目前,Swift不提供型別錯誤(在其他語言中稱為“已檢查的異常”,如Java),這意味著丟擲的任何函式都可能丟擲任何錯誤。雖然這為我們提供了很大的靈活性,但在使用API時,對於生產程式碼和測試來說,這也是一個挑戰。
考慮以下函式,該函式通過從URL同步載入資料來執行操作:
class AClass {
enum SearchError: Error {
case invalidParameters(String)
}
func loadData(_ parameters: String) throws -> Data {
let urlString = "https://host/q=\(parameters)"
guard let url = URL(string: urlString) else {
throw SearchError.invalidParameters(parameters)
}
return try Data(contentsOf: url)
}
複製程式碼
正如您在上面所看到的,我們的函式可以結束在兩個不同的位置(嘗試構造URL時和使用URL 初始化Data時)。所以,這就是問題所在; 作為一個API使用者,我很不清楚我可以期待這個函式丟擲什麼樣的錯誤。我不需要知道這個函式在Data內部使用哪些型別,但我還需要知道Data初始化程式可以丟擲哪些錯誤。
在API設計中,必須瞭解imlpementation細節通常是一個不好的跡象,所以如果我們能保證我們的函式只丟擲SearchError型別的錯誤,那不是更好嗎?幸運的是,它很容易修復。我們所要做的就是將對資料的呼叫包裝在一個do, try, catch塊中。重構以後的程式碼,像這樣:
class AClass {
enum SearchError: Error {
case invalidParameters(String)
case loadingFailed(URL)
}
func loadData(_ parameters: String) throws -> Data {
let urlString = "https://host/q=\(parameters)"
guard let url = URL(string: urlString) else {
throw SearchError.invalidParameters(parameters)
}
do {
return try Data(contentsOf: url)
} catch {
throw SearchError.loadingFailed(url)
}
}
}
複製程式碼
我們上面所做的是將初始化Data丟擲的異常替換為我們自己的錯誤。現在,我們可以記錄我們的函式總是丟擲一個SearchError,並且我們的API在錯誤處理方面變得更容易使用。
###然而,在使我們的API變得更好的同時,我們的實現也變得混亂了。通常,您需要使用do, try, catch塊包裝多個呼叫,這將使我們的程式碼很快變得難以閱讀。為了解決這個問題,我建立了一個簡單的函式來執行此包裝,並在丟擲基礎錯誤時丟擲特定錯誤。它看起來像這樣:
func perform<T>(_ expression: @autoclosure () throws -> T, orThrow error: Error) throws -> T {
do {
return try expression()
} catch let error {
throw error
}
}
複製程式碼
###使用它,我們現在可以從新更新我們的loadData功能,使其更簡單:
func loadData(_ parameters: String) throws -> Data {
let urlString = "https://host/q=\(parameters)"
guard let url = URL(string: urlString) else {
throw SearchError.invalidParameters(parameters)
}
return try perform(Data(contentsOf: url), orThrow: SearchError.loadingFailed(url))
}
複製程式碼
我們現在有一個統一的錯誤API和一個更簡單的實現!
這種處理問題的思路,在Alamofire中也有體現,如JSONEncoding中的encode函式
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters else { return urlRequest }
do {
let data = try JSONSerialization.data(withJSONObject: parameters, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
複製程式碼
DataRequest中Requestable也有所體現:
func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
do {
let urlRequest = try self.urlRequest.adapt(using: adapter)
return queue.sync { session.dataTask(with: urlRequest) }
} catch {
throw AdaptError(error: error)
}
}
複製程式碼
如果您有任何問題,建議或反饋,請隨時與我聯絡~