错误处理
错误处理就是响应错误以及从错误中回复的过程
Swift
提供了运行时对可恢复错误的抛出、捕获、传递和操作等的支持
通常用来区分失败情况,让程序解决并处理某些错误,把解决不了的错误报告给用户
表示并抛出错误
Swift
中 错误用符合Error
协议的类型值来表示,这个空协议表明该类型可以用于错误处理
通常用枚举类型来构建一组相关的错误状态,枚举的关联值可以提供错误状态的额外信息
使用关键字throw
关键字来抛出一个错误,表示有意外发生
enum VendingMachineError: Error {
case invalidSelection //选择无效
case insufficientFunds(coinsNeeded: Int) //金额不足
case outOfStock //缺货
}
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
处理错误
Swift
中有4
种处理错误的方式
1.把函数抛出的错误传递给调用此函数的代码
2.用do-catch
语句处理错误
3.将错误作为可选类型处理
4.断言此错误根本不会发生
当一个函数抛出错误是,程序流程发生改变,所以重要的是能迅速识别代码中抛出错误的地方。为了标识这些地方,在调用一个能跑出错误的函数、方法或者构造器之前,加上try
关键字,或者try?
或try!
这种变体。
注意:
Swift中的错误处理和其他语言的
try
、catch
、throw
进行异常处理很像。但是和其他语言(包括OC)的异常处理不同的是swift
的错误处理不涉及解除调用栈,这是一个计算代价高昂的过程。就此而言,throw
语句的性能特点是可以和return
语句相媲美的
用throwing函数传递错误
在函数声明的参数列表后加上throws
关键字,表明可以抛出错误,这个函数就称为throwing
函数
如果这个函数指明了返回值类型,throws
关键词需要写在箭头(->
)的前面
throw语句执行会立即退出方法
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
throwing
函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
注意:
对于
throwing
函数可以传递错误,非throwing
函数抛出的错误只能在函数内部处理
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 dispenseSnack(snack: String) {
print("Dispensing \(snack)")
}
//throwing函数 会抛出VendingMachineError错误
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)")
}
}
使用了guard
语句来提前退出方法,因为throw
语句会立即退出方法,保证只有在满足所有条件时才成功卖出商品
vend(itemNamed:)
会传递它抛出的所有错误,因此在跳用这个方法时,要么直接处理这些错误(do-catch
语句,try?
或try!
),要么类似下面函数将错误继续传递下去
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)
}
//传似抛出的错误
buyFavoriteSnack
方法会查找某人最喜欢的零食,并通过调用vend(itemNamed:)
方法尝试购买,通过在vend(itemNamed:)
方法前加try
关键字
throwing
构造器和throwing
一样可以传递错误
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
用Do-Catch处理错误
do-catch
语句运行一段闭包
代码来处理错误
在do
语句抛出一个错误,与catch
中的字句匹配
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
catch
后面写一个匹配模式来表明这个子句能处理什么样的错误.如果catch
没有指定错误模式,那就可以匹配任何错误,并且把错误绑定到名字为error
的局部变量
catch
不必处理抛出的所有错误,错误可以传递到周围,但是必须被处理,可以使外围的do-catch
语句 或者是一个throwing
函数进行处理
注意:
在do
语句中的try
如果有抛出错误就立刻执行catch
语句,并判断这个错误是否要被继续传递下去,否则执行do
子句中余下的语句
将错误转换成可选值
通过try?
将错误转换为一个可选值,如果表达式有错误抛出,那么表达式的值就为nil
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
//等效于
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
此时不论someThrowingFunction()
返回值是什么类型,x,y就是该返回值类型的可选类型
禁用错误传递
当我们确定知道某个throwing
函数在运行时是不会抛出错误的,那么可以用tr
来禁用错误传递,它会将调用包装在一个不会有错误抛出的运行时断言中
y!
指定清理操作
defer
语句在即将离开当前代码块时执行一系列语句,来执行一些必要的清理工作,而不管你是由于哪种方式离开代码块
defer
语句将代码执行延迟到当前作用域退出之前。- 该语句由
defer
关键字和要延时执行的语句组成 - 延迟执行的语句不能包含任何控制转移语句例如return、break,或是抛出一个错误。
- 延时执行的操作会按照他们被指定时的顺序的相反顺序执行(第一条
defer
语句中的代码会在第二条defer
语句中的代码被执行之后才执行)
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 处理文件。
}
// close(file) 会在这里被调用,即作用域的最后。
}
}