> 2.0 翻譯+校對:[lyojo](https://github.com/lyojo)
本頁內容包含:
[TOC]
錯誤處理是響應錯誤以及從錯誤中返回的過程。swift提供第一類錯誤支持,包括在運行時拋出,捕獲,傳送和控制可回收錯誤。
一些函數和方法不能總保證能夠執行所有代碼或產生有用的輸出。可空類型用來表示值可能為空,但是當函數執行失敗的事后,可空通常可以用來確定執行失敗的原因,因此代碼可以正確地響應失敗。在Swift中,這叫做拋出函數或者拋出方法。
舉個例子,考慮到一個從磁盤上的一個文件讀取以及處理數據的任務,有幾種情況可能會導致這個任務失敗,包括指定路徑的文件不存在,文件不具有可讀屬性,或者文件沒有被編碼成合適的格式。區分這些錯誤可以讓程序解決并且修復這些錯誤,并且,如果可能的話,把這些錯誤報告給用戶。
> 注意: Swift中的錯誤處理涉及到錯誤處理樣式,這會用到Cocoa中的NSError和Objective-C。更多信息請參見:[Using Swift with Cocoa and Objective-C](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/index.html#//apple_ref/doc/uid/TP40014216)中的[錯誤處理](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)。
### 錯誤的表示:
在Swift中,錯誤用符合`ErrorType`協議的值表示。 Swift枚舉特別適合把一系列相關的錯誤組合在一起,同時可以把一些相關的值和錯誤關聯在一起。因此編譯器會為實現`ErrorType`協議的Swift枚舉類型自動實現相應合成。
比如說,你可以這樣表示操作自動販賣機會出現的錯誤:
~~~
enum VendingMachineError: ErrorType {
case InvalidSelection
case InsufficientFunds(required: Double)
case OutOfStock
}
~~~
在這種情況下,自動販賣機可能會因為以下原因失敗: 請求的物品不存在,用`InvalidSelection`表示。 請求的物品的價格高于已投入金額,用`InsufficientFunds`表示。相關的雙精度值表示還需要多少錢來完成此次交易。 請求的物品已經賣完了,用`OutOfStock`表示。
錯誤拋出 通過在函數或方法聲明的參數后面加上`throws`關鍵字,表明這個函數或方法可以拋出錯誤。如果指定一個返回值,可以把`throws`關鍵字放在返回箭頭(->)的前面。除非明確地指出,一個函數,方法或者就閉包不能拋出錯誤。
~~~
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
~~~
在拋出函數體的任意一個地方,可以通過`throw`語句拋出錯誤。在下面的例子中,如果請求的物品不存在,或者賣完了,或者超出投入金額,`vend(itemNamed:)`函數會拋出一個錯誤:
~~~
struct Item {
var price: Double
var count: Int
}
var inventory = [
"Candy Bar": Item(price: 1.25, count: 7),
"Chips": Item(price: 1.00, count: 4),
"Pretzels": Item(price: 0.75, count: 11)
]
var amountDeposited = 1.00
func vend(itemNamed name: String) throws {
guard var item = inventory[name] else {
throw VendingMachineError.InvalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.OutOfStock
}
if amountDeposited >= item.price {
// Dispense the snack
amountDeposited -= item.price
--item.count
inventory[name] = item
} else {
let amountRequired = item.price - amountDeposited
throw VendingMachineError.InsufficientFunds(required: amountRequired)
}
}
~~~
首先,`guard`語句用來把綁定`item`常量和`count`變量到在庫存中對應的值。如果物品不在庫存中,將會拋出`InvalidSelection`錯誤。然后,物品是否可獲取有物品的剩余數量決定。如果`count`小于等于0,將會拋出`OutOfStock`錯誤。最后,把請求物品的價格和已經投入的金額進行比較,如果如果投入的金額大于物品的價格,將會從投入的金額從減去物品的價格,然后庫存中該物品的數量減1,然后返回請求的物品。否則,將會計算還需要多少錢,然后把這個值作為`InsufficientFunds`錯誤的關聯值。因為`throw`語句會馬上改變程序流程,當所有的購買條件(物品存在,庫存足夠以及投入金額足夠)都滿足的時候,物品才會出售。
當調用一個拋出函數的時候,在調用前面加上`try`。這個關鍵字表明函數可以拋出錯誤,而且在`try`后面代碼將不會執行。
~~~
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vend(itemNamed: snackName)
}
~~~
`buyFavoriteSnack(_:)`?函數查找某個人的最喜歡的零食,然后嘗試買給他。如果這個人在列表中沒有喜歡的零食,就會購買`Candy Bar`。這個函數會調用`vend`函數,`vend`函數可能會拋出錯誤,所以在`vend`前面加上了`try`關鍵字。因為`buyFavoriteSnack`函數也是一個拋出函數,所以`vend`函數拋出的任何錯誤都會向上傳遞到`buyFavoriteSnack`被調用的地方。
### 捕捉和處理錯誤
使用do-catch語句來就捕獲和處理錯誤
~~~
do {
try function that throws
statements
} catch pattern {
statements
}
~~~
如果一個錯誤被拋出了,這個錯誤會被傳遞到外部域,直到被一個`catch`分句處理。一個`catch`分句包含一個`catch`關鍵字,跟著一個`pattern`來匹配錯誤和相應的執行語句。
類似`switch`語句,編譯器會檢查`catch`分句是否能夠處理全部錯誤。如果能夠處理所有錯誤情況,就認為這個錯誤被完全處理。否者,包含這個拋出函數的所在域就要處理這個錯誤,或者包含這個拋出函數的函數也用`throws`聲明。為了保證錯誤被處理,用一個帶`pattern`的`catch`分句來匹配所有錯誤。如果一個`catch`分句沒有指定樣式,這個分句會匹配并且綁定任何錯誤到一個本地`error`常量。更多關于`pattern`的信息,參見[模式](http://wiki.jikexueyuan.com/project/swift/chapter3/07_Patterns.html)。
~~~
do {
try vend(itemNamed: "Candy Bar")
// Enjoy delicious snack
} catch VendingMachineError.InvalidSelectio {
print("Invalid Selection")
} catch VendingMachineError.OutOfStock {
print("Out of Stock.")
} catch VendingMachineError.InsufficientFunds(let amountRequired) {
print("Insufficient funds. Please insert an additional $\(amountRequired).")
}
~~~
在上面的例子中,`vend(itemNamed:)`?函數在`try`表達式中被調用,因為這個函數會拋出錯誤。如果拋出了錯誤,程序執行流程馬上轉到`catch`分句,在`catch`分句中確定錯誤傳遞是否繼續傳送。如果沒有拋出錯誤,將會執行在`do`語句中剩余的語句。
> 注意:Swift中的錯誤處理和其他語言中的異常處理很像,使用了`try`、`catch`和`throw`關鍵字。但是和這些語言——包括Objective-C——不同的是,Swift不會展開調用堆棧,那會帶來很大的性能損耗。因此,在Swift中`throw`語句的性能可以做到幾乎和`return`語句一樣。
### 禁止錯誤傳播
在運行時,有幾種情況拋出函數事實上是不會拋出錯誤的。在這幾種情況下,你可以用`forced-try`表達式來調用拋出函數或方法,即使用`try!`來代替`try`。
通過`try!`來調用拋出函數或方法禁止了錯誤傳送,并且把調用包裝在運行時斷言,這樣就不會拋出錯誤。如果錯誤真的拋出了,會觸發運行時錯誤。
~~~
func willOnlyThrowIfTrue(value: Bool) throws {
if value { throw someError }
}
do {
try willOnlyThrowIfTrue(false)
} catch {
// Handle Error
}
try! willOnlyThrowIfTrue(false)
~~~
### 收尾操作
使用defer語句來在執行一系列的語句。這樣不管有沒有錯誤發生,都可以執行一些必要的收尾操作。包括關閉打開的文件描述符以及釋放所有手動分配的內存。
`defer`語句把執行推遲到退出當前域的時候。`defer`語句包括`defer`關鍵字以及后面要執行的語句。被推遲的語句可能不包含任何將執行流程轉移到外部的代碼,比如`break`或者`return`語句,或者通過拋出一個錯誤。被推遲的操作的執行的順序和他們定義的順序相反,也就是說,在第一個`defer`語句中的代碼在第二個`defer`語句中的代碼之后執行。
~~~
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// Work with the file.
}
// close(file) is called here, at the end of the scope.
}
}
~~~
上面這個例子使用了`defer`語句來保證`open`有對應的`close`。這個調用不管是否有拋出都會執行。
- 介紹
- 歡迎使用 Swift
- 關于 Swift
- Swift 初見
- Swift 版本歷史記錄
- Swift1.0 發布內容
- Swift 教程
- 基礎部分
- 基本運算符
- 字符串和字符
- 集合類型
- 控制流
- 函數
- 閉包
- 枚舉
- 類和結構體
- 屬性
- 方法
- 下標腳本
- 繼承
- 構造過程
- 析構過程
- 自動引用計數
- 可選鏈
- 錯誤處理
- 類型轉換
- 嵌套類型
- 擴展
- 協議
- 泛型
- 權限控制
- 高級操作符
- 語言參考
- 關于語言參考
- 詞法結構
- 類型
- 表達式
- 語句
- 聲明
- 特性
- 模式
- 泛型參數
- 語法總結
- 蘋果官方Blog官方翻譯
- Access Control 權限控制的黑與白
- 造個類型不是夢-白話Swift類型創建
- WWDC里面的那個“大炮打氣球”
- Swift與C語言指針友好合作
- 引用類型和值類型的恩怨
- 訪問控制和Protected
- 可選類型完美解決占位問題