Swift 錯誤類型的種類
Simple domain error
簡單的,顯而易見的錯誤。這種錯誤的最大特點是我們不需要關心原因,只需要知道錯誤發生,並且想要進行處理。用來表示這種錯誤發生的方法一般就是返回一個 nil
值。在 Swift 中,這類錯誤最常見的情況就是將某個字符串轉換爲整數,或者在字典嘗試用某個不存在的 key
獲取元素:
// Simple Domain Error 的例子
let num = Int("hello world") // nil
let element = dic["key_not_exist"] // nil
在使用層面 (或者說應用邏輯) 上,這類錯誤一般用 if let
的可選值綁定或者是 guard let
提前進行返回處理即可,不需要再在語言層面上進行額外處理。
Recoverable error
正如其名,這類錯誤應該是被容許,並且是可以恢復的。可恢復錯誤的發生是正常的程序路徑之一,而作爲開發者,我們應當去檢出這類錯誤發生的情況,並進一步對它們進行處理,讓它們恢復到我們期望的程序路徑上。
這類錯誤在 Objective-C 的時代通常用 NSError
類型來表示,而在 Swift 裏則是 throw
和 Error
的組合。一般我們需要檢查錯誤的類型,並作出合理的響應。而選擇忽視這類錯誤往往是不明智的,因爲它們是用戶正常使用過程中可能會出現的情況,我們應該嘗試對其恢復,或者至少向用戶給出合理的提示,讓他們知道發生了什麼。像是網絡請求超時,或者寫入文件時磁盤空間不足:
// 網絡請求
let url = URL(string: "https://www.example.com/")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
// 提示用戶
self.showErrorAlert("Error: \(error.localizedDescription)")
}
let data = data!
// ...
}
// 寫入文件
func write(data: Data, to url: URL) {
do {
try data.write(to: url)
} catch let error as NSError {
if error.code == NSFileWriteOutOfSpaceError {
// 嘗試通過釋放空間自動恢復
removeUnusedFiles()
write(data: data, to: url)
} else {
// 其他錯誤,提示用戶
showErrorAlert("Error: \(error.localizedDescription)")
}
} catch {
showErrorAlert("Error: \(error.localizedDescription)")
}
}
Universal error
這類錯誤理論上可以恢復,但是由於語言本身的特性所決定,我們難以得知這類錯誤的來源,所以一般來說也不會去處理這種錯誤。這類錯誤包括類似下面這些情形:
// 內存不足
[Int](repeating: 100, count: .max)
// 調用棧溢出
func foo() { foo() }
foo()
我們可以通過設計一些手段來對這些錯誤進行處理,比如:檢測當前的內存佔用並在超過一定值後警告,或者監視棧 frame 數進行限制等。但是一般來說這是不必要的,也不可能涵蓋全部的錯誤情況。更多情況下,這是由於代碼觸碰到了設備的物理限制和邊界情況所造成的,一般我們也不去進行處理(除非是人爲造成的 bug)。
**在 Swift 中,各種被使用 fatalError 進行強制終止的錯誤一般都可以歸類到 Universal error。
**
Logic failure
邏輯錯誤是程序員的失誤所造成的錯誤,它們應該在開發時通過代碼進行修正並完全避免,而不是等到運行時再進行恢復和處理。
常見的 Logic failure 包括有:
// 強制解包一個 `nil` 可選值
var name: String? = nil
name!
// 數組越界訪問
let arr = [1,2,3]
let num = arr[3]
// 計算溢出
var a = Int.max
a += 1
// 強制 try 但是出現錯誤
try! JSONDecoder().decode(Foo.self, from: Data())
這類錯誤在實現中觸發的一般是 assert
或者 precondition
。
斷言的作用範圍和錯誤轉換
和 fatalError
不同,assert
只在進行編譯優化的 -O
配置下是不觸發的,而如果更進一步,將編譯優化選項配置爲 -Ounchecked
的話,precondition
也將不觸發。此時,各方法中的 precondition
將被跳過,因此我們可以得到最快的運行速度。但是相對地代碼的安全性也將降低,因爲對於越界訪問或者計算溢出等錯誤,我們得到的將是不確定的行爲。
函數 | fatalError | precondition | assert |
---|---|---|---|
-Onone | 觸發 | 觸發 | 觸發 |
-O | 觸發 | 觸發 | |
-Ounchecked | 觸發 |
對於 Universal error
一般使用 fatalError
,而對於 Logic failure
一般使用 assert
或者 precondition
。遵守這個規則會有助於我們在編碼時對錯誤進行界定。而有時候我們也希望能儘可能多地在開發的時候捕獲 Logic failure,而在產品發佈後儘量減少 crash 比例。這種情況下,相比於直接將 Logic failure 轉換爲可恢復的錯誤,我們最好是使用 assert
在內部進行檢查,來讓程序在開發時崩潰。
本文轉載自喵神的 Blog: https://onevcat.com/2017/10/swift-error-category/