Files
the-swift-programming-langu…/source/chapter2/18_Error_Handling.md
2015-07-21 17:56:10 +08:00

142 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 错误处理
-----------------
错误处理是响应错误以及从错误中返回的过程。swift提供第一类错误支持包括在运行时抛出捕获传送和控制可回收错误。
一些函数和方法不能总保证能够执行所有代码或产生有用的输出。可空类型用来表示值可能为空但是当函数执行失败的事后可空通常可以用来确定执行失败的原因因此代码可以正确地响应失败。在Swift中这叫做抛出函数或者抛出方法。
举个例子,考虑到一个从磁盘上的一个文件读取以及处理数据的任务,有几种情况可能会导致这个任务失败,包括指定路径的文件不存在,文件不具有可读属性,或者文件没有被编码成合适的格式。区分这些错误可以让程序解决并且修复这些错误,并且,如果可能的话,把这些错误报告给用户。
>
NOTE
Swift中的错误处理涉及到错误处理样式这会用到Cocoa中的NSError和Objective-C。更多信息请参见[Error Handling](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/BuildingCocoaApps/AdoptingCocoaDesignPatterns.html#//apple_ref/doc/uid/TP40014216-CH7-ID10)中的[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)。
###错误的表示:
在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的信息参见[Patterns](https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Patterns.html#//apple_ref/doc/uid/TP40014097-CH36-ID419)。
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语句中剩余的语句。
###禁止错误传送
在运行时有几种情况抛出函数事实上是不会抛出错误的。在这几种情况下你可以用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。这个调用不管是否有抛出都会执行。