從 Swift 5 開始,導入了一種新型別,Result type
正如他的名字一樣,Result 就是結果的意思,白話一點就是 O 或 X 啦
而他是透過 enum 來定義的,讓我們來看一下
enum Result<Success, Failure> where Failure : Error {
/// A success, storing a `Success` value.
case success(Success)
/// A failure, storing a `Failure` value.
case failure(Failure)
}
可以看到透過 Generic (泛型) 定義 Success
跟 Failure
其中 Failure
限制需遵守 Protocol Error
以及透過 associated value 來將資料儲存起來,方便後續使用
那 Result type 可以用來做什麼呢?
像是一般進行 API 呼叫時,常會用的 URLSession 就是一個很好的例子
我們先來看一下常見的 URLSession.shared.dataTask
open func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
open func dataTask(with url: URL, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
可以看到 completionHandler 內定義了 (Data?, URLResponse?, Error?) -> Void
並不能透過 throws 將 API 呼叫中所遇到的錯誤丟出來
(當然啦,你也可以透過寫一個 failure 的 closure 來處理)
這時就可以透過 Result type 來進行改寫了!
這裡我以 OpenWeatherAPI 來做示範
我們先來看一下,不使用 Result type 時,API 該如何呼叫
enum WeatherDataFetchError: Error {
case invalidURL
case requestFailed
case responseFailed
case jsonDecodeFailed
}
func getWeatherData(city: String, completion: @escaping (CurrentWeatherData?, WeatherDataFetchError?) -> Void) {
let address = "https://api.openweathermap.org/data/2.5/weather?"
let apikey = "YOUR_API_KEY"
guard let url = URL(string: address + "q=\(city)" + "&appid=" + apikey) else {
completion(nil, .invalidURL)
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completion(nil, .requestFailed)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200, let data = data else {
completion(nil, .responseFailed)
return
}
let decoder = JSONDecoder()
guard let weatherData = try? decoder.decode(CurrentWeatherData.self, from: data) else {
completion(nil, .jsonDecodeFailed)
return
}
completion(weatherData, nil)
}.resume()
}
看起來好像還好,沒有哪邊特別的,跟平常寫的差不多啊
那我們先看一下改用 Result type 後的寫法
enum WeatherDataFetchError: Error {
case invalidURL
case requestFailed
case responseFailed
case jsonDecodeFailed
}
func getWeatherData(city: String, completion: @escaping (Result<CurrentWeatherData, WeatherDataFetchError>) -> Void) {
let address = "https://api.openweathermap.org/data/2.5/weather?"
let apikey = "YOUR_API_KEY"
guard let url = URL(string: address + "q=\(city)" + "&appid=" + apikey) else {
completion(.failure(.invalidURL))
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
completion(.failure(.requestFailed))
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200, let data = data else {
completion(.failure(.responseFailed))
return
}
let decoder = JSONDecoder()
guard let weatherData = try? decoder.decode(CurrentWeatherData.self, from: data) else {
completion(.failure(.jsonDecodeFailed))
return
}
completion(.success(weatherData))
}.resume()
}
雖然看起來感覺感覺沒什麼差,也沒有減少行數,那是不是繼續用原先的寫法就好啦
其實還是有差別的!像是原先透過 Closure 的寫法,也是可以,但會有個地方怪怪的
就是 CurrentWeatherData (天氣資料) 跟 WeatherDataFetchError (API 呼叫中遇到的錯誤)
這兩個結果應該只會出現一種,而不會出現像下面這個表格的狀況
CurrentWeatherData | WeatherDataFetchError |
---|---|
O | X |
X | O |
O | O |
X | X |
這時候就是 Result type 出現的時候了
上面是在實際 API 在執行的地方,那呼叫的地方又該如何寫呢
一樣,我們先看一般 API 呼叫的時候,該如何處理
WeatherAPIService.shared.getWeatherData(city: city) { weatherData, weatherFetchError in
if weatherFetchError != nil {
switch weatherFetchError {
case .invalidURL:
print("無效的 URL")
case .requestFailed:
print("Request Error")
case .responseFailed:
print("Response Error")
case .jsonDecodeFailed:
print("JSON Decode 失敗")
case .none:
break
}
} else {
DispatchQueue.main.async {
// 對 UI 進行對應處理
}
}
}
改用 Result type 後的 API 呼叫
WeatherAPIService.shared.getWeatherData(city: city) { result in
switch result {
case .success(let weatherData):
DispatchQueue.main.async {
// 對 UI 進行對應處理
}
case.failure(let fetchError):
switch fetchError {
case .invalidURL:
print("無效的 URL")
case .requestFailed:
print("Request Error")
case .responseFailed:
print("Response Error")
case .jsonDecodeFailed:
print("JSON Decode 失敗")
}
}
}
可以看到改用 Result type 後,程式整體來說可讀性更好了!
- https://developer.apple.com/documentation/swift/result
- https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/%E6%88%90%E5%8A%9F%E5%92%8C%E5%A4%B1%E6%95%97%E4%BA%8C%E6%93%87%E4%B8%80%E7%9A%84-result-type-e234c6fccc9c
- https://medium.com/@god913106/ios-swift-5-%E6%96%B0%E5%8A%9F%E8%83%BD-result-type-3f6d6528865b
- https://juejin.cn/post/6844903805184638990
- https://medium.com/%E5%BD%BC%E5%BE%97%E6%BD%98%E7%9A%84-swift-ios-app-%E9%96%8B%E7%99%BC%E5%95%8F%E9%A1%8C%E8%A7%A3%E7%AD%94%E9%9B%86/enum-%E5%84%B2%E5%AD%98%E7%9B%B8%E9%97%9C%E8%81%AF%E8%B3%87%E6%96%99%E7%9A%84-associated-value-26ab3e061a16