表示与抛出错误
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枚举很好地匹配了相关错误条件的模型,枚举中包含了相关的值。举个例子:
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语句来抛出一个错误。举个例子:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
错误处理
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.
swift有四种方式处理错误。你可以把一个函数中的错误传给调用此函数的代码,使用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 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!。
使用throwing函数传递错误
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 throws keyword before the return arrow (->).
为了表明一个函数,方法,或初始化方法可以抛出错误,在函数声明的参数后面加上throws关键字。用throws标记的函数被称为throwing函数。如果此函数有一个返回类型,throws关键字要写在返回箭头(->)之前:
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
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:
下面的例子中,VendingMachine类有一个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(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.
vend(itemNamed:)方法的实现使用了guard语句来提前退出方法并抛出一个相应的错误,如果方法需要的条件没有得到满足的话。
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?或者try!---要么继续传递错误。比如,下面例子中的buyFavoriteSnack(person: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)
}
Throwing initializers can propagate errors in the same way as throwing functions.
throwing初始化方法可以用同样的方式传递错误。
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
使用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分句捕获,并判断由哪个分句来处理此错误。
下面是do-catch语句的一般格式:
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.
在catch后写上模式(pattern),以此表明那个分句能处理什么错误。如果一个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语句都不处理一个错误,那个错误会被传递到周围的代码(surrounding scope)。但是,此错误必须被周围的代码所处理—不管是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(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语句的剩下代码。
把错误转换为可选值
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。比如,下面代码中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的值就是函数的返回值。注意x和y都是可选的,不管someThrowingFunction()函数返回什么类型。例子中,someThrowingFunction()返回一个整数,因此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
}
禁用错误传递
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")
指定清理操作
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 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关键字和之后要执行的代码。被延迟的语句或许不包含任何可能把控制权转移出当前语句的代码:比如一个break或return语句,或抛出一个错误。被延迟的操作以他们被指定的顺序的逆序进行执行—即,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(:)调用。