异常捕捉(Error Handling)
Swift的异常处理是在程序中响应错误和处理错误恢复程序正常运行的过程。Swift提供了一流的抛出、捕获、传播和操作可恢复的过程。
有的操作不是每次都能执行成功的,而可选链是用来判断有没有值,但操作失败时,无法知道失败的原因。错误处理就是帮助你了解失败的原因,方便你做出相应的反应。
例如,从磁盘上读取和处理文件数据的任务时,会由文件不存在于指定的路径,或文件没有读取权限,或文件没有以兼容格式编码等原因造成此任务失败。通过异常捕获机制,可以有效的解析是什么原因造成任务失败,并作出对应的操作。
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协议。
1. 声明错误枚举值
例如,以下是如何表示在游戏中操作自动售货机的错误条件:
// 错误枚举,需Error协议
enum VendingMachineError: Error {
//错误类型
case invalidSelection //无效的选择
case insufficientFunds(coinsNeeded: Int) //资金不足
case outOfStock //缺货
}
2. 抛出错误
抛出错误可以指示发生的意外事件,并且无法继续执行正常的流程。使用throw语句抛出一个错误。例如,以下代码引发错误,以指示自动售货机需要五个额外的硬币:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
3. 处理错误
当抛出错误时,就必须对改错误做处理。例如,通过纠正问题,使用替代方法或通知用户失败。
使用四种方法处理错误,传播的错误函数来调用该函数的代码、使用do-catch
语句处理错误、将错误作为一个可选值、或断言错误不会发生。
- 传播的错误函数来调用该函数的代码(Propagating Errors Using Throwing Functions)
使用throws
关键字修饰可能会引发错误的方法,如例:
//有返回值的抛出函数,抛出函数将在其中抛出的错误传播到它所调用的作用域
func canThrowErrors() throws -> String { return "1" }
//无返回值的抛出函数
func canThrowErrors() throws { }
//不能抛出的函数,在该函数内抛出的任何错误都必须在改函数内处理
func cannotThrowErrors() -> String { return "2" }
在下面的示例中,VendingMachine
类有一个vend(itemNamed:)
方法,当请求的item
不可用、或缺货、或成本超过当前存款金额,则抛出VendingMachineError
。
enum VendingMachineError: Error {
case invalidSelection
case insufficientMoney(coinsNeeded:Int)
case outOfStack
}
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校验,条件为假则执行else内的语句
//如果以name对应字符串为Key,不能在inventory中取出的Item类型的值,则抛出错误
guard let item = inventory[name] else {
throw VendingMachineError.invalidSelection
}
//item的count不大于0,则抛出错误
guard item.count > 0 else {
throw VendingMachineError.outOfStack
}
//如果item的price大于coinsDeposited,则抛出异常
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientMoney(coinsNeeded: item.price - coinsDeposited)
}
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("Dispensing \(name)")
}
}
该vend(itemNamed:)
方法的实现使用guard
语句,如果购买小吃的任一要求不满足则提前退出该方法,并且抛出适当的错误。只有满足所有时时,方法才会执行完,项目才会被销售。
因为vend(itemNamed:)
方法传播它抛出的任何错误,所以调用此方法的必须使用do- catch语句
、或try?
、或try!
处理错误,或者继续传递。例如,下面的例子中buyFavoriteSnack(person:vendingMachine:)
也是一个抛出函数,调用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) //因为该vend(itemNamed:)方法可以抛出一个错误,它的前面使用try关键字修饰。
}
抛出初始化器可以与抛出函数相同的方式传播错误。例如,下面PurchasedSnack
初始化器调用抛出函数作为初始化过程的一部分,它通过其调用者传播处理它遇到的任何错误。
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
- 使用Do-Catch处理错误(Handling Errors Using Do-Catch)
通过使用do- catch语句,运行代码块来处理错误。执行的代码放在do
的括号内,执行可抛出错误的代码使用try,如果do
的代码中抛出一个错误,使用catch
来匹配条件,在错误匹配成功后,还要匹配where后的语句才会执行内部代码。如下所示:
do {
tryexpression
//如果没有try到错误,继续执行剩下的语句
statements
} catchpattern 1
{
//如果没有pattern 1
,则只要try到错误都会执行该括号内的语句
statements
} catchpattern 2
wherecondition
{
statements
}
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."
在上面的例子中,以上代码处理枚举的所有三种情况VendingMachineError,但所有其他错误必须由其周围范围处理。buyFavoriteSnack(person:vendingMachine:)
函数在表达式中被调用try,因为它可能会抛出一个错误。如果抛出错误,立即转移到catch执行子句,决定是否允许继续传播。如果没有抛出错误,则执行语句中的do剩余语句。
- 将错误转换为可选值(Converting Errors to Optional Values)
可以通过try?将错误转换为可选值来处理错误。如果在计算表达式时抛出错误,则表达式try?的值为nil。例如,在下面的代码中x,y具有相同的值:
//如果someThrowingFunction()抛出一个错误,价值x和y为nil
func someThrowingFunction() throws -> Int {
// ...
}
//函数返回整数可能是一个nil,所以x和y是Int?类型的整数
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
当所有的错误用同样的方式来处理时,使用try?
更加简洁。
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
- 禁用错误传播(Disabling Error Propagation)
当确定一个函数或方法不会抛出错误时,可以使用try!
来禁用错误传播。但如果调用时抛出错误,则会报错。
例如,以下代码使用一个loadImage(atPath:)
函数,该函数使用给定路径下加载图像资源,或者如果无法加载图像,则会抛出错误。在这种情况下,由于图片随应用程序一起提供,因此在运行时不会抛出错误,因此禁用错误传播是适当的。
let photo = try! loadImage(atPath:"./Resources/John Appleseed.jpg")
4. 指定清除操作(Specifying Cleanup Actions)
使用defer
语句将语句内的代码放到同一代码块最后执行,第一个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.
}
}
let k = 12
var str = "12"
if k==12 {
print("1")
defer {
print("2")
}
while let j = Int(str) {
print(j)
str = "KK"
}
}//输出 1 12 3
参考链接
用来记录平时遇到的问题,不对之处还望指教。