天天看點

【iOS 開發】NSError ** 與 throws 的三個問題

問題一:為什麼有錯誤處理還要傳回值?

NSFileManager

裡面有這樣一個方法:

- (BOOL)removeItemAtURL:(NSURL *)URL error:(NSError **)error;
           

使用的時候我們會傳入一個

&error

再擷取這個錯誤值,來看這個過程中有沒有什麼錯誤,那麼通過

error == nil

不就可以知道是否執行成功嗎,為什麼需要

BOOL

傳回值,這是一個備援的設計嗎?

考慮下面這種情況:

NSData *data = nil;
NSError *error = nil;
BOOL success = [data writeToURL:nil options:NSDataWritingAtomic error:&error];
           

我們會發現,由于 data 是 nil,這個方法會直接傳回 0,但是 error 依然是 nil,是以官方文檔也要求我們一定要通過傳回值判斷是否執行成功,而不是僅僅去對 error 判空。

另外,基于 Objective-C 的語言特性,這裡我們無法阻止調用者對 error 參數傳遞 nil,但是這個方法在這種情況下依然需要告知調用者是否執行成功,是以傳回值是一個必要的設計。

然而,下面我們會發現,雖然這不是一個備援設計,但是這也不是一個好的設計。

問題二:如何做出一個沒有傳回值的錯誤處理?

上面那個方法在 Swift 中是這樣的:

func removeItem(atPath path: String) throws
           

沒有傳回值

Objective-C 中為了對外部建立的 NSError 指派,使用了雙指針設計,即 NSError *__autoreleasing*,這種做法在 Swift 語言中,變成了 inout 關鍵字:

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}
           

這實作了在函數中修改參數值,按照這種寫法,是不是我們可以臆想出一種完全對應于 Objective-C 風格的版本:

func removeItem(atPath path: String) throws // 原版
func removeItem(atPath path: String, error: inout NSError) -> Bool // 臆想版本
           

理論上或許可行,但是這裡我臆想出的這個版本,和 OC 中這個方法的設計,都是不好的設計:為了友善,很多時候開發者會對 error 傳入

nil,這使得一旦出錯,這裡的 Error Handling 是無效的,而當初這裡

傳入 nil 也正是因為開發者認為這種同步方法不像異步的網絡請求那樣容易出錯,最終就是艱難的 bug 排查。

Swift 2 引入的異常機制強迫我們使用下面的這種做法,

let fileManager = FileManager.default
do {
    try fileManager.removeItem(atPath: filePath)
} catch {
    print(error)
}
           

這樣使得錯誤更加容易被發現和處理,并且由于 Swift 是強類型語言,在這裡 nil 并不能執行 removeItem 方法,是以在這裡,沒有傳回值卻成了合理的設計。

但有一點需要注意,在這裡我們隻能擷取到一個 error,我們卻無法知道可以擷取到一個什麼樣的 error,我們無法直接通過 API 知道,假如這裡 removeItem 不成功,到底可能是因為什麼樣的原因而導緻不成功。

問題三:throws 是同步的,異步的時候怎麼辦?

答:向

Error?

低頭。

func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask
           

error

An error object that indicates why the request failed, or nil if the request was successful.

由于

try catch

是一種同步的文法,在異步的時候,我們還是隻能通過 Error 或者 NSError 來判斷執行是否成功。

一種更好的做法其實是封裝枚舉,像這樣:

enum JSONError: Error {
    case noSuchKey(String)
    case typeMismatch
}
           

對于這種做法可以參考

antitypical/Result

,而如果你一定要使用原生 API,記得看一眼文檔吧,到底 return value、error、responseData 中哪個值可以保證你的操作是成功的。

參考連結:

繼續閱讀