Error handling is the process of responding to and recovering from error conditions in your program. Swift provides first-class support for throwing, catching, propagating, and manipulating recoverable errors at runtime. Some operations aren’t guaranteed to always complete execution or produce a useful output. Optionals are used to represent the absence of a value, but when an operation fails, it’s often useful to understand what caused the failure, so that your code can respond accordingly. As an example, consider the task of reading and processing data from a file on disk. There are a number of ways this task can fail, including the file not existing at the specified path, the file not having read permissions, or the file not being encoded in a compatible format. Distinguishing among these different situations allows a program to resolve some errors and to communicate to the user any errors it can’t resolve. 举例,在读取和处理磁盘上的一个文件的数据时,会有有许多方法失败,包括指定的文件路径找不到,没有文件的读取权限,或文件编码格式不兼容。在这些不同情况下,就可以让程序提示用户导致程序无法执行的具体原因。

Representing and Throwing Errors

In Swift, errors are represented by values of types that conform to the Error protocol. This empty protocol indicates that a type can be used for error handling. 在swift中,错误类型是遵循Error 协议。

Swift enumerations are particularly well suited to modeling a group of related error conditions, with associated values allowing for additional information about the nature of an error to be communicated. For example, here’s how you might represent the error conditions of operating a vending machine inside a game: swift枚举类型特别适合为一组错误条件建模,用来关联导致错误的真正原因的相关信息。例如: 一个在操作一台游戏机时的会出现的错误枚举类:

1
2
3
4
5
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}

Throwing an error lets you indicate that something unexpected happened and the normal flow of execution can’t continue. You use a throw statement to throw an error. For example, the following code throws an error to indicate that five additional coins are needed by the vending machine: 抛出错误说明游戏出现异常,导致其他操作无法进行。这是需要通过Throw语句来抛出这个错误。例如,以下代码抛出了一个错误表明需要five:

1
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

Handling Errors

When an error is thrown, some surrounding piece of code must be responsible for handling the error—for example, by correcting the problem, trying an alternative approach, or informing the user of the failure. 当错误抛出后,这段代码必须来处理这个错误。例如:通过纠正问题,尝试其他方式实现,或通知用户的失败。

There are four ways to handle errors in Swift. You can propagate the error from a function to the code that calls that function, handle the error using a do-catch statement, handle the error as an optional value, or assert that the error will not occur. Each approach is described in a section below. 在swift中有四种处理错误机制: 1. 向调用方法中传递这个错误 2. 使用do-catch语句处理 3. 把错误设置为可选型 4.断言不会出现异常的情况下,使用try!禁止异常抛出

When a function throws an error, it changes the flow of your program, so it’s important that you can quickly identify places in your code that can throw errors. To identify these places in your code, write the try keyword—or the try? or try! variation—before a piece of code that calls a function, method, or initializer that can throw an error. These keywords are described in the sections below. 当一个方法抛出错误时,会打断程序正常的工作流,必须快速定位到可能抛出错误的代码。可以使用关键字try 要注意 try?try!之间的差异。在调用一个函数,方法或者构造器之前,来抛出异常。

Propagating Errors Using Throwing Functions

To indicate that a function, method, or initializer can throw an error, you write the throws keyword in the function’s declaration after its parameters. A function marked with throws is called a throwing function. If the function specifies a return type, you write the throwskeyword before the return arrow (->). 函数,方法或构造器都可以抛出异常,只需要在声明它们时添加关键字:throws即可,这种方法被称为throws函数,throws关键字位置在参数之后,返回值(->)之前

A throwing function propagates errors that are thrown inside of it to the scope from which it’s called. 抛出函数会把错误抛给调用它的函数周期中去。

Only throwing functions can propagate errors. Any errors thrown inside a nonthrowing function must be handled inside the function. 只有抛出函数能传递错误,如果不是抛出函数,它自己必须处理掉任何抛进来的错误。

In the example below, the VendingMachine class has a vend(itemNamed:) method that throws an appropriate VendingMachineError if the requested item is not available, is out of stock, or has a cost that exceeds the current deposited amount: 在下面的例子中,对某个请求内容不可用时,自动售货机会通过vend(itemNamed:)方法抛出一个对应的VendingMachineError错误,缺货或超过目前的存款金额成本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct Item {
    var price: Int
    var count: Int
}
class VendingMachine
{
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0
    func vend(itemNamed name: String) throws 
    {
        guard let item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }
        guard item.count > 0 else {
            throw VendingMachineError.outOfStock
        }
        guard item.price <= coinsDeposited else {
            throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }
        coinsDeposited -= item.price
        var newItem = item
        newItem.count -= 1
        inventory[name] = newItem
        print("Dispensing \(name)")
    }
}

The implementation of the vend(itemNamed:) method uses guard statements to exit the method early and throw appropriate errors if any of the requirements for purchasing a snack aren’t met. Because a throw statement immediately transfers program control, an item will be vended only if all of these requirements are met. vend(itemNamed:)方法的实现中使用 guard语句来判断,当在购买操作不符合相应条件会直接抛出相应的错误,来打断该方法的其他操作。因为throw语句会立即转移程序控制权,保证了机器仅出售满足所有条件的物品。

Because the vend(itemNamed:) method propagates any errors it throws, any code that calls this method must either handle the errors—using a do-catch statement, try?, or try!—or continue to propagate them. For example, the buyFavoriteSnack(person:vendingMachine:) in the example below is also a throwing function, and any errors that the vend(itemNamed:) method throws will propagate up to the point where the buyFavoriteSnack(person:vendingMachine:) function is called. 因为vend(itemNamed:)的方法抛出所有错误,所以调用该方法的函数必须处理错误( do-catch, try?, or try!处理,或使用try向上抛出)。

例如,buyFavoriteSnack(person:vendingMachine:)也是一个抛出函数,从vend(itemNamed:)方法接受到的所有错误,都会被会buyFavoriteSnack(person:vendingMachine:)继续抛给调用它的函数中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws 
{
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}

In this example, the buyFavoriteSnack(person: vendingMachine:) function looks up a given person’s favorite snack and tries to buy it for them by calling the vend(itemNamed:) method. Because the vend(itemNamed:) method can throw an error, it’s called with the trykeyword in front of it. buyFavoriteSnack(person: vendingMachine:) 函数查找买家中意的snake,并vend(itemNamed:)调用尝试购买。使用在方法前使用try关键字来抛出异常,并向上传递。

Throwing initializers can propagate errors in the same way as throwing functions. For example, the initializer for the PurchasedSnackstructure in the listing below calls a throwing function as part of the initialization process, and it handles any errors that it encounters by propagating them to its caller. 抛出构造器也能向抛出函数一样传递错误,例如:PurchasedSnackstructure的构造器中调用了抛出函数:vend(itemNamed:),抛出构造器可以通过向上传递来处理这些错误。

1
2
3
4
5
6
7
8
struct PurchasedSnack {
    let name: String
    init(name: String, vendingMachine: VendingMachine) throws 
    {
        try vendingMachine.vend(itemNamed: name)
        self.name = name
    }
}

Handling Errors Using Do-Catch

You use a do-catch statement to handle errors by running a block of code. If an error is thrown by the code in the do clause, it is matched against the catch clauses to determine which one of them can handle the error. 使用do-catch语句通过运行代码块来处理错误。在do分句中如果抛出了一个错误,那么就可以在catch分句中进行处理的匹配到错误 Here is the general form of a do-catch statement:

1
2
3
4
5
6
7
8
do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
}

You write a pattern after catch to indicate what errors that clause can handle. If a catch clause doesn’t have a pattern, the clause matches any error and binds the error to a local constant named error. For more information about pattern matching, see Patterns. 在关键字catch后边跟随匹配模式,这个catch分句就会处理匹配到的错误,如果关键字catch分句没有任何匹配模式,那么这个分句将会匹配到所有错误,并把这些错误信息赋值给系统常量error

The catch clauses don’t have to handle every possible error that the code in its do clause can throw. If none of the catch clauses handle the error, the error propagates to the surrounding scope. However, the error must be handled by some surrounding scope—either by an enclosing do-catch clause that handles the error or by being inside a throwing function. For example, the following code handles all three cases of the VendingMachineError enumeration, but all other errors have to be handled by its surrounding scope: catch分句不必对do分句中的代码可能抛出每一个的错误,如果某个错误没有被catch分句匹配到处理,这个错误将会传递到调用它的函数周期中,这个错误必须在这个函数周期中处理,或通过do-catch语句来处理,或通过内部的抛出函数处理。

例如,下面的代码处理VendingMachineError枚举类中的三个错误cases,但其他的错误都是由其周边范围处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.invalidSelection {
    print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
    print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
}
// Prints "Insufficient funds. Please insert an additional 2 coins."

In the above example, the buyFavoriteSnack(person:vendingMachine:) function is called in a try expression, because it can throw an error. If an error is thrown, execution immediately transfers to the catch clauses, which decide whether to allow propagation to continue. If no error is thrown, the remaining statements in the do statement are executed. 在上述代码中因为buyFavoriteSnack(person:vendingMachine:)函数会抛出错误异常,所以要在try表达式中调用。如果抛出异常就会立马执行是否继续传递的catch分句,如果没有抛出异常,会保持do分句中的代码正常进行。

Converting Errors to Optional Values

You use try? to handle an error by converting it to an optional value. If an error is thrown while evaluating the try? expression, the value of the expression is nil. For example, in the following code x and y have the same value and behavior: 使用try?表达式处理错误,是通过把错误转换为可选值(?)类型处理。在try?表达式中出现抛出错误时,这个表达式的值=nil

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
func someThrowingFunction() throws -> Int {
    // ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
    y = try someThrowingFunction()
} catch {
    y = nil
}

If someThrowingFunction() throws an error, the value of x and y is nil. Otherwise, the value of x and y is the value that the function returned. Note that x and y are an optional of whatever type someThrowingFunction() returns. Here the function returns an integer, so xand y are optional integers. 如果函数someThrowingFunction()抛出异常,x,y = nil,否则:x,y就时函数return的值。

注意:x,y是一个可选的someThrowingFunction()返回类型。在这里函数返回integer,那么x,y是可选的integer类型。

Using try? lets you write concise error handling code when you want to handle all errors in the same way. For example, the following code uses several approaches to fetch data, or returns nil if all of the approaches fail. 当用这种方式来处理所有错误时,使用try?表达式能写更简洁的错误处理代码,例如:下面的代码使用几种方法来获取数据,如果这些方法失败就会返回nil。

1
2
3
4
5
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}

Disabling Error Propagation

Sometimes you know a throwing function or method won’t, in fact, throw an error at runtime. On those occasions, you can write try! before the expression to disable error propagation and wrap the call in a runtime assertion that no error will be thrown. If an error actually is thrown, you’ll get a runtime error. 有时你认为不会出现异常的抛出函数或方法,但事实上,却在运行时抛出异常了。在其他情况下,可以使用try!表达式来禁止错误传递,并且十分肯定断言在运行时不会有异常抛出。如果有异常抛出,就会得到一个运行时错误。

For example, the following code uses a loadImage(atPath:) function, which loads the image resource at a given path or throws an error if the image can’t be loaded. In this case, because the image is shipped with the application, no error will be thrown at runtime, so it is appropriate to disable error propagation. 例如:使用loadImage(atPath:)函数要么通过这个路径加载image,要么image加载失败抛出一个错误。在这种情况下,因为image在app资源目录resources中,所以在运行时肯定不会有异常抛出。因此可以使用try!禁止错误传递。

1
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

Specifying Cleanup Actions

You use a defer statement to execute a set of statements just before code execution leaves the current block of code. This statement lets you do any necessary cleanup that should be performed regardless of how execution leaves the current block of code—whether it leaves because an error was thrown or because of a statement such as return or break. For example, you can use a defer statement to ensure that file descriptors are closed and manually allocated memory is freed. 在代码执行离开当前代码块之前,使用一个“defer语句”来执行一组语句。defer语句让做一些必要的清理操作,在执行如何离开当前代码块:因为抛出异常或者因为return,break语句导致。例如:使用defer语句执行关闭文件和内存的释放操作,来确保清理操作完成。

A defer statement defers execution until the current scope is exited. This statement consists of the defer keyword and the statements to be executed later. The deferred statements may not contain any code that would transfer control out of the statements, such as a break or a return statement, or by throwing an error. Deferred actions are executed in reverse order of how they are specified—that is, the code in the first defer statement executes after code in the second, and so on. defer语句推迟在直到当前作用域推出时,再执行。defer语句defer关键字和一些清理操作语句组成。defer语句不包含transfer control out of the statements例如(return,break,throw)等。

defer延迟操作是按照指定的顺序执行的,就是先执行最后一个defer语句中的代码之后,再执行第倒数第二个defer语句的代码,以此类推,倒叙清理退场。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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.
        }
}

The above example uses a defer statement to ensure that the open(_:) function has a corresponding call to close(_:). 使用defer语句,确保了在open的同时与之相关的还有close。

You can use a defer statement even when no error handling code is involved. 即使在没有涉及错误处理代码的时候,也可以使用“延迟”语句。