异常的由来
在写代码的过程中,我们不能保证自己的每一行代码,都能够正确地执行。不能保证每一个函数,都会返回我们所期望的值。所以很多编程语言,都引入了异常处理
机制,来让我们得知执行失败的原因,从而可以做出应对。
举例:在读取某个磁盘中的文件时,可能遇到
打开磁盘失败
,文件不存在
等错误。
使用可选来处理简单的异常
在 Swift 中有可选
的机制,可以解决函数返回不成功的问题,我们经常写出如下代码:
/// 计算
func caculator() -> Double? {
// XXX
}
然后调用这个函数
if let result = caculator() {
print(result)
} else {
print("计算错误")
}
通过可选
机制,可以应对一些简单的错误处理。当返回了正确的值,我们可以进行下一步操作,当返回nil
,则表示计算过程中发生了错误。但是当出现问题的原因比较多的时候,nil
的表现力就显得很弱。所以,接下来介绍异常处理
。
认识异常处理
假设有这样一个场景:汽车启动,当使用的钥匙正确,才能开启汽车。当燃油大于5升才可以成功启动,否则启动不成功。
我们首先定义一个枚举,来描述汽车启动失败可能的每种情况。针对燃油不足的情况,我们还设置了关联值来提醒用户还差多少油才能启动。
// 描述汽车出问题的各种情况
enum CarError: Error {
case keyError // 钥匙不对
case outOfFuel(fuelNeed: Double) // 燃油不足
}
接着定义汽车模型。
struct Car {
// 当前燃油
var fuelInLitre: Double
// 汽车的钥匙
var keyName: String
// 启动汽车
func start() throws -> String {
guard keyName == "trueKey" else {
throw CarError.keyError
}
guard fuelInLitre > 5 else {
throw CarError.outOfFuel
}
return "启动成功"
}
}
假设正确的钥匙名称为trueKey
。这里使用了throws
关键字,来表示start
方法在执行过程中,可能会抛出异常。throws
需要写在->
前面。
接着,尝试调用以上的代码。
let car = Car(fuelInLitre: 6, keyName: "trueKey")
do {
let message = try car.start()
print(message)
} catch CarError.keyError {
print("钥匙不正确")
} catch CarError.outOfFuel(let fuelNeed) {
print("燃油不足,还需要:\(fuelNeed)")
} catch {
print("发生了其他错误")
}
我们先是定义了一个Car
的实例,接着尝试调用start
方法,因为这个方法可能会抛出异常,所以必须包含在do
的代码块里面,在调用方法前面加上try
关键字。调用完毕后,我们就当这个方法能调用成功,继续写成功之后的代码,输出启动信息。
接着使用catch
关键字,来捕获可能抛出的异常,并对异常进行处理,我们可能会打印错误日志,或者提示一些有用的信息。
事实上我们只定义了两种可能发生的异常,但是编译时Xcode 9
会报错,必须在最后面再加一个catch
来表示发了生其他的错误,有点类似于switch
语句最后那个default
。
其他补充
除了try
关键字,还有try?
和try!
关键字。try?
和可选机制很类似,当方法抛出异常,就会返回一个nil
。
还是上面汽车启动的例子,我们可以这样写:
let car = Car(fuelInLitre: 3, keyName: "trueKey")
if let message = try? car.start() {
print(message)
} else {
print("启动错误,具体原因不知")
}
就不去判断那么多的异常情况,如果返回nil
,就代表发生了异常,具体原因不关心。
try!
关键字和强制解包很类似,当你认为某个方法一定能执行成功不会抛出异常时,就用它。但是万一抛出了异常,那么程序就崩溃。
let car = Car(fuelInLitre: 6, keyName: "trueKey")
// 强制执行,如果失败,程序直接崩溃
let message = try! car.start()
print(message)
在介绍defer
关键字之前,假设:当我们尝试开启一辆汽车,无论启动成功,或是启动失败,都会在最后关闭车门。首先为Car
模型新增一个方法closeDoor()
。
func closeDoor() {
print("关好车门")
}
然后使用defer
关键字,调用。
// 打开了汽车的车门
let car = Car(fuelInLitre: 6, keyName: "trueKey")
// 延时执行,无论 start 方法执行成功或者失败,我们都调用方法来关闭车门
defer {
car.closeDoor()
}
do {
let message = try car.start()
print(message)
} catch CarError.keyError {
print("钥匙不正确")
} catch CarError.outOfFuel(let fuelNeed) {
print("燃油不足,还需要:\(fuelNeed)")
} catch {
print("发生了其他错误")
}
defer
关键字,当你的代码无论是正常的执行了,或者是抛出了异常,都会去调用defer
代码块里面的语句,多用于做一些清理的工作。
上面的例子比较牵强,另外举一个十分常见的场景:我们尝试打开磁盘,去读取某个文件时,无论读取成功还是失败,都会在最后关闭磁盘。
网友评论