Merge pull request #826 from WAMaker/gh-pages

Update "17_Error_Handling" for Swift 4.2
This commit is contained in:
Karsa Wu
2018-11-07 21:19:33 +08:00
committed by GitHub

View File

@ -1,21 +1,21 @@
# 错误处理 # 错误处理
*错误处理Error handling*是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一等公民支持 *错误处理Error handling* 是响应错误以及从错误中恢复的过程。Swift 在运行时提供了抛出、捕获、传递和操作可恢复错误recoverable errors的一等支持first-class support
某些操作无法保证总是执行完所有代码或总是生成有用的结果。可选类型用来表示值缺失,但是当某个操作失败时,最好能得知失败的原因,从而可以作出相应的应对。 某些操作无法保证总是执行完所有代码或生成有用的结果。可选类型用来表示值缺失,但是当某个操作失败时,理解造成失败的原因有助于你的代码作出相应的应对。
举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。 举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序处理并解决某些错误,然后把它解决不了的错误报告给用户。
> 注意 > 注意
> >
> Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的 `NSError`。关于这个类的更多信息请参见 [Using Swift with Cocoa and Objective-C (Swift 4.1)](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 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的 `NSError`。更多详情参见 [用 Swift 解决 Cocoa 错误](https://developer.apple.com/documentation/swift/cocoa_design_patterns/handling_cocoa_errors_in_swift)。
<a name="representing_and_throwing_errors"></a> <a name="representing_and_throwing_errors"></a>
## 表示抛出错误 ## 表示抛出错误
在 Swift 中,错误用符合 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。 在 Swift 中,错误用遵循 `Error` 协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。
Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,你可以这样表示在一个游戏中操作自动贩卖机时可能会出现的错误状态: Swift 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,游戏中操作自动贩卖机时,你可以这样表示可能会出现的错误状态:
```swift ```swift
enum VendingMachineError: Error { enum VendingMachineError: Error {
@ -25,7 +25,7 @@ enum VendingMachineError: Error {
} }
``` ```
抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用 `throw` 关键字。例如,下面的代码抛出一个错误,提示贩卖机还需要 `5` 个硬币: 抛出一个错误可以让你表明有意外情况发生,导致正常的执行流程无法继续执行。抛出错误使用 `throw` 语句。例如,下面的代码抛出一个错误,提示贩卖机还需要 `5` 个硬币:
```swift ```swift
throw VendingMachineError.insufficientFunds(coinsNeeded: 5) throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
@ -47,10 +47,11 @@ Swift 中有 `4` 种处理错误的方式。你可以把函数抛出的错误传
<a name="propagating_errors_using_throwing_functions"></a> <a name="propagating_errors_using_throwing_functions"></a>
### 用 throwing 函数传递错误 ### 用 throwing 函数传递错误
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作*throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在箭头(`->`)的前面。 为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数之后加上 `throws` 关键字。一个标有 `throws` 关键字的函数被称作 *throwing 函数*。如果这个函数指明了返回值类型,`throws` 关键词需要写在返回箭头(`->`)的前面。
```swift ```swift
func canThrowErrors() throws -> String func canThrowErrors() throws -> String
func cannotThrowErrors() -> String func cannotThrowErrors() -> String
``` ```
@ -64,8 +65,8 @@ func cannotThrowErrors() -> String
```swift ```swift
struct Item { struct Item {
var price: Int var price: Int
var count: Int var count: Int
} }
class VendingMachine { class VendingMachine {
@ -75,9 +76,6 @@ class VendingMachine {
"Pretzels": Item(price: 7, count: 11) "Pretzels": Item(price: 7, count: 11)
] ]
var coinsDeposited = 0 var coinsDeposited = 0
func dispenseSnack(snack: String) {
print("Dispensing \(snack)")
}
func vend(itemNamed name: String) throws { func vend(itemNamed name: String) throws {
guard let item = inventory[name] else { guard let item = inventory[name] else {
@ -103,7 +101,7 @@ class VendingMachine {
} }
``` ```
`vend(itemNamed:)` 方法的实现中使用了 `guard` 语句来提前退出方法,确保在购买某个物品所需的条件中有任一条件不满足时,能提前退出方法并抛出相应的错误。由于 `throw` 语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。 `vend(itemNamed:)` 方法的实现中使用了 `guard` 语句来确保在购买某个物品所需的条件中有任一条件不满足时,能提前退出方法并抛出相应的错误。由于 `throw` 语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。
因为 `vend(itemNamed:)` 方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用 `do-catch` 语句,`try?``try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(person:vendingMachine:)` 同样是一个 throwing 函数,任何由 `vend(itemNamed:)` 方法抛出的错误会一直被传递到 `buyFavoriteSnack(person:vendingMachine:)` 函数被调用的地方。 因为 `vend(itemNamed:)` 方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用 `do-catch` 语句,`try?``try!`;要么继续将这些错误传递下去。例如下面例子中,`buyFavoriteSnack(person:vendingMachine:)` 同样是一个 throwing 函数,任何由 `vend(itemNamed:)` 方法抛出的错误会一直被传递到 `buyFavoriteSnack(person:vendingMachine:)` 函数被调用的地方。
@ -147,29 +145,57 @@ do {
statements statements
} catch pattern 2 where condition { } catch pattern 2 where condition {
statements statements
} catch {
statements
} }
``` ```
`catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。 `catch` 后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条 `catch` 子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为 `error` 的局部常量。关于模式匹配的更多信息请参考 [模式](../chapter3/07_Patterns.html)。
`catch` 子句不必将 `do` 子句中的代码所抛出的每一个可能的错误都作处理。如果所有 `catch` 子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的 `do-catch` 错误处理语句,要么是一个 throwing 函数的内部。举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部枚举值,但是所有其它的错误就必须由它周围的作用域处理 举例来说,下面的代码处理了 `VendingMachineError` 枚举类型的全部三种情况
```swift ```swift
var vendingMachine = VendingMachine() var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8 vendingMachine.coinsDeposited = 8
do { do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine) try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
print("Success! Yum.")
} catch VendingMachineError.invalidSelection { } catch VendingMachineError.invalidSelection {
print("Invalid Selection.") print("Invalid Selection.")
} catch VendingMachineError.outOfStock { } catch VendingMachineError.outOfStock {
print("Out of Stock.") print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) { } catch VendingMachineError.insufficientFunds(let coinsNeeded) {
print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.") print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
print("Unexpected error: \(error).")
} }
// 打印 “Insufficient funds. Please insert an additional 2 coins.” // 打印 “Insufficient funds. Please insert an additional 2 coins.”
``` ```
上面的例子中,`buyFavoriteSnack(person:vendingMachine:)` 函数在一个 `try` 表达式中调用,因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到 `catch` 子句中,并判断这个错误是否要被继续传递下去。如果没有错误抛出,`do` 子句中余下的语句就会被执行。 上面的例子中,`buyFavoriteSnack(person:vendingMachine:)` 函数在一个 `try` 表达式中调用,因为它能抛出错误。如果错误被抛出,相应的执行会马上转移到 `catch` 子句中,并判断这个错误是否要被继续传递下去。如果错误没有被匹配,它会被最后一个 `catch` 语句捕获,并赋值给一个 `error` 常量。如果没有错误抛出,`do` 子句中余下的语句就会被执行。
`catch` 子句不必将 `do` 子句中的代码所抛出的每一个可能的错误都作处理。如果所有 `catch` 子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的。在不会抛出错误的函数中,必须用 `do-catch` 语句处理错误。而能够抛出错误的函数既可以使用 `do-catch` 语句处理,也可以让调用方来处理错误。如果错误传递到了顶层作用域却依然没有被处理,你会得到一个运行时错误。
以下面的代码为例,不是 `VendingMachineError` 中申明的错误会在调用函数的地方被捕获:
```swift
func nourish(with item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch is VendingMachineError {
print("Invalid selection, out of stock, or not enough money.")
}
}
do {
try nourish(with: "Beet-Flavored Chips")
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
// 打印 "Invalid selection, out of stock, or not enough money."
```
如果 `vend(itemNamed:)` 抛出的是一个 `VendingMachineError` 类型的错误,`nourish(with:)` 会打印一条消息,否则 `nourish(with:)` 会将错误抛给它的调用方。这个错误之后会被通用的 `catch` 语句捕获。
### 将错误转换成可选值 ### 将错误转换成可选值