Error Handling (错误处理)

Error handlingis 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.

错误处理(Error handling)是响应错误以及从错误中恢复的过程。Swift 提供了在运行时对可恢复错误的抛出、捕获、传递和操作的一等公民支持。

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.

举个例子,假如有个从磁盘上的某个文件读取数据并进行处理的任务,该任务会有多种可能失败的情况,包括指定路径下文件并不存在,文件不具有可读权限,或者文件编码格式不兼容。区分这些不同的失败情况可以让程序解决并处理某些错误,然后把它解决不了的错误报告给用户。

NOTE

Error handling in Swift interoperates with error handling patterns that use theNSErrorclass in Cocoa and Objective-C. For more information about this class, seeError HandlinginUsing Swift with Cocoa and Objective-C (Swift 3.0.1).

Swift 中的错误处理涉及到错误处理模式,这会用到 Cocoa 和 Objective-C 中的NSError。关于这个类的更多信息请参见Using Swift with Cocoa and Objective-C (Swift 3.0.1)中的错误处理

Representing and Throwing Errors (表示并抛出错误)

In Swift, errors are represented by values of types that conform to theErrorprotocol. 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 的枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。例如,你可以这样表示在一个游戏中操作自动贩卖机时可能会出现的错误状态:

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 athrowstatement 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关键字。例如,下面的代码抛出一个错误,提示贩卖机还需要5个硬币:

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 ado-catchstatement, handle the error as an optional value, or assert that the error will not occur. Each approach is described in a section below.

Swift 中有4种处理错误的方式。你可以把函数抛出的错误传递给调用此函数的代码、用do-catch语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。每种方式在下面的小节中都有描述。

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 thetrykeyword—or thetry?ortry!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!这种变体。这些关键字在下面的小节中有具体讲解。

NOTE

Error handling in Swift resembles exception handling in other languages, with the use of thetry,catchandthrowkeywords. Unlike exception handling in many languages—including Objective-C—error handling in Swift does not involve unwinding the call stack, a process that can be computationally expensive. As such, the performance characteristics of athrowstatement are comparable to those of areturnstatement.

Swift 中的错误处理和其他语言中用try,catch和throw进行异常处理很像。和其他语言中(包括 Objective-C )的异常处理不同的是,Swift 中的错误处理并不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,throw语句的性能特性是可以和return语句相媲美的。

Propagating Errors Using Throwing Functions (用 throwing 函数传递错误)

To indicate that a function, method, or initializer can throw an error, you write thethrowskeyword in the function’s declaration after its parameters. A function marked withthrowsis called athrowing function. If the function specifies a return type, you write thethrowskeyword before the return arrow (->).

为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数列表之后加上throws关键字。一个标有throws关键字的函数被称作throwing 函数。如果这个函数指明了返回值类型,throws关键词需要写在箭头(->)的前面。

func canThrowErrors()throws->String

func cannotThrowErrors() ->String

A throwing function propagates errors that are thrown inside of it to the scope from which it’s called.

一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。

NOTE

Only throwing functions can propagate errors. Any errors thrown inside a nonthrowing function must be handled inside the function.

只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。

In the example below, theVendingMachineclass has avend(itemNamed:)method that throws an appropriateVendingMachineErrorif the requested item is not available, is out of stock, or has a cost that exceeds the current deposited amount:

下面的例子中,VendingMechine类有一个vend(itemNamed:)方法,如果请求的物品不存在、缺货或者投入金额小于物品价格,该方法就会抛出一个相应的VendingMachineError:

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(itemNamedname:String)throws{

        guard let item=inventory[name]else{

            throwVendingMachineError.invalidSelection

        }

        guard item.count>0else{

          throwVendingMachineError.outOfStock

        }

    guard item.price<=coinsDepositedelse{

         throwVendingMachineError.insufficientFunds(coinsNeeded:item.price-coinsDeposited)

    }

coinsDeposited-=item.price

var newItem=item

newItem.count-=1

inventory[name] =newItem

print("Dispensing\(name)")

    }

}

The implementation of thevend(itemNamed:)method usesguardstatements to exit the method early and throw appropriate errors if any of the requirements for purchasing a snack aren’t met. Because athrowstatement immediately transfers program control, an item will be vended only if all of these requirements are met.

在vend(itemNamed:)方法的实现中使用了guard语句来提前退出方法,确保在购买某个物品所需的条件中,有任一条件不满足时,能提前退出方法并抛出相应的错误。由于throw语句会立即退出方法,所以物品只有在所有条件都满足时才会被售出。

Because thevend(itemNamed:)method propagates any errors it throws, any code that calls this method must either handle the errors—using ado-catchstatement,try?, ortry!—or continue to propagate them. For example, thebuyFavoriteSnack(person:vendingMachine:)in the example below is also a throwing function, and any errors that thevend(itemNamed:)method throws will propagate up to the point where thebuyFavoriteSnack(person:vendingMachine:)function is called.

因为vend(itemNamed:)方法会传递出它抛出的任何错误,在你的代码中调用此方法的地方,必须要么直接处理这些错误——使用do-catch语句,try?或try!;要么继续将这些错误传递下去。例如下面例子中,buyFavoriteSnack(_:vendingMachine:)同样是一个 throwing 函数,任何由vend(itemNamed:)方法抛出的错误会一直被传递到buyFavoriteSnack(person:vendingMachine:)函数被调用的地方。

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, thebuyFavoriteSnack(person: vendingMachine:)function looks up a given person’s favorite snack and tries to buy it for them by calling thevend(itemNamed:)method. Because thevend(itemNamed:)method can throw an error, it’s called with thetrykeyword in front of it.

上例中,buyFavoriteSnack(person:vendingMachine:)函数会查找某人最喜欢的零食,并通过调用vend(itemNamed:)方法来尝试为他们购买。因为vend(itemNamed:)方法能抛出错误,所以在调用的它时候在它前面加了try关键字。

Throwing initializers can propagate errors in the same way as throwing functions. For example, the initializer for thePurchasedSnackstructure 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.

throwing构造器能像throwing函数一样传递错误.例如下面代码中的PurchasedSnack构造器在构造过程中调用了throwing函数,并且通过传递到它的调用者来处理这些错误。

struct PurchasedSnack{

    let name:String

    init(name:String,vendingMachine:VendingMachine)throws{

        try vendingMachine.vend(itemNamed:name)

        self.name=name 

    }

}

Handling Errors Using Do-Catch (用 Do-Catch 处理错误)

You use ado-catchstatement to handle errors by running a block of code. If an error is thrown by the code in thedoclause, it is matched against thecatchclauses to determine which one of them can handle the error.

可以使用一个do-catch语句运行一段闭包代码来处理错误。如果在do子句中的代码抛出了一个错误,这个错误会与catch子句做匹配,从而决定哪条子句能处理它。

Here is the general form of ado-catchstatement:

下面是do-catch语句的一般形式:

do {

    try expression

    statements

} catch pattern 1{

    statements

} catch pattern 2wherecondition{

    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, seePatterns.

在catch后面写一个匹配模式来表明这个子句能处理什么样的错误。如果一条catch子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为error的局部常量。关于模式匹配的更多信息请参考模式

The catch clauses don’t have to handle every possible error that the code in itsdoclause 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 VendingMachineErrorenumeration, but all other errors have to be handled by its surrounding scope:

catch子句不必将do子句中的代码所抛出的每一个可能的错误都作处理。如果所有catch子句都未处理错误,错误就会传递到周围的作用域。然而,错误还是必须要被某个周围的作用域处理的——要么是一个外围的do-catch错误处理语句,要么是一个 throwing 函数的内部。举例来说,下面的代码处理了VendingMachineError枚举类型的全部枚举值,但是所有其它的错误就必须由它周围的作用域处理:

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(letcoinsNeeded) {

    print("Insufficient funds. Please insert an additional\(coinsNeeded)coins.")

}

// Prints "Insufficient funds. Please insert an additional 2 coins."

In the above example, the buy FavoriteSnack(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 isnil. For example, in the following code x and y have the same value and behavior:

可以使用try?通过将错误转换成一个可选值来处理错误。如果在评估try?表达式时一个错误被抛出,那么表达式的值就是nil。例如,在下面的代码中,x和y有着相同的数值和等价的含义:

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 x and y are optional integers.

如果someThrowingFunction()抛出一个错误,x和y的值是nil。否则x和y的值就是该函数的返回值。注意,无论someThrowingFunction()的返回值类型是什么类型,x和y都是这个类型的可选类型。例子中此函数返回一个整型,所以x和y是可选整型。

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:

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.

有时你知道某个throwing函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写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:)函数,该函数从给定的路径加载图片资源,如果图片无法载入则抛出一个错误。在这种情况下,因为图片是和应用绑定的,运行时不会有错误抛出,所以适合禁用错误传递:

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语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如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 are turn 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关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如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.

}

}

The above example uses a defer statement to ensure that the open(_:)function has a corresponding call to close(_:).

上面的代码使用一条defer语句来确保open(_:)函数有一个相应的对close(_:)函数的调用。

NOTE

You can use a deferstatement even when no error handling code is involved.

即使没有涉及到错误处理,你也可以使用defer语句。

推荐阅读更多精彩内容