Swift-错误处理
关键字: throws、throw、 try、 try?、 try!、 do-catch、defer
错误处理 是指对代码中的异常情况, 作出响应的过程. swift 在运行时对错误的抛出、捕获、传递、操作提供了一级支持
开发过程中, 有些操作往往不能保证一定成功, 在失败时, 我们需要知道失败的原因, 因此, 便需要错误处理以便做出相应的响应.
例如: 从硬盘上读取文件时, 有多种原因会导致读取操作失败: 文件不存在、没有读取权限、文件格式不能正确编码等, 用不同的错误来区分这些状态, 可以让你的程序正确处理, 并能告诉用户失败的原因
对于错误表示, OC中用NSError, 而在swift中, 用Error
表示错误
在swift中, 使用遵守Error协议的类型来表示错误.
/// A type representing an error value that can be thrown.
public protocol Error {
}
Error 协议实际上是空的, 只是用来表示遵守该协议的某类型可以用于错误处理
注意: 抛出不遵守Error协议的类型时, 编译器会报错
枚举特别适用于封装错误, 可以并利用关联值特性, 来关联相关的错误信息, 如
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
抛出错误
使用关键字throw来执行抛出错误的操作, throw 后边的类型必须遵守Error协议, 否则报编译错误
抛出一个还需要5枚硬币的错误, 就可以这样:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
处理错误
要表明一个函数、方法 或 构造器 可能会抛出错误, 可以使用关键字throws 声明在函数的形参之后, 则该函数即为throwing函数.
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
对于throwing函数, 调用时必须要处理错误, 否则报编译错误: Errors thrown from here are not handled
处理错误的方式有4种:
传递错误
捕获错误
转换错误为可选类型
断言错误
当函数抛出错误时, 会改变程序的当前执行流程, 因此在代码中确认抛出错误的位置显得尤为重要, 要确认抛出异常的位置,就需要使用关键字try, 或者try?, try!, 声明在函数调用前面.
注意:swift中的错误处理和其它语言的try catch throw类似, 但是与OC的异常处理不同, swift不会展开调用栈, 展开调用栈是一个很耗费性能的进程, 因此, throw 语句的性能与return语句差不多
传递错误:
throwing函数 会将在其内部抛出的错误传递到调用的代码块中, 如:
enum MyError: Error {
case error1, error2, error3
}
func throw1() throws {
throw MyError.error3
}
func throw2() throws {
do {
try throw1()
} catch MyError.error3 {
print("error3")
}
}
func excute() {
do {
try throw2()
} catch {
print(error)
}
}
excute()
throw1抛出的错误, 会传递到它的调用者throw2中, 如果throw2不能对该错误进行处理,则错误继续传递到throw2的调用者excute中, 在excute中进行处理
捕获错误
使用do-catch语句来捕获抛出的错误
如传递错误示例代码:
func excute() {
do {
try throw2()
doSomething()
} catch {
print(error)
}
}
do语句块中, 执行throwing函数throw2的调用
如果有错误抛出, 程序就会由do语句块转移到 catch语句, try throw2()之后的doSomething就不会执行了
如果没有错误, 则顺序执行do语句块之后的doSomething
注意: catch语句如果没有指定匹配的错误值, 默认会有一个本地的error变量
转换错误为可选类型
使用关键字try?来讲一个错误转换为可选类型
当有错误抛出时, 整个try? 表达式的值为nil, 如下, x 和 y的值相同
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
注意: 使用try?处理错误, 如果有返回值, 则不管返回值是什么类型, 都会加一层可选类型封装, 即Int变成Int?, Int?变成Int??
断言错误
使用关键字try!, 可以禁用错误传递, 因为系统会封装一层没有错误抛出的运行时断言, 如果有错误抛出, 则断言失败, 中断程序,报运行时错误
注意: 该方法适用于确定可以成功操作的情况, 使用时务必小心, 因为一旦判断不严谨, 会造成app崩溃
与Cocoa的桥接
Swift会将Cocoa中带error参数的方法, 转换为throwing函数
在Cocoa中, 表示错误, 通常使用NSError指针作为方法的最后一个参数, Swift会检查OC方法声明, 翻译为Swift的throwing函数, 函数名字甚至可以更短
例如, 移除文件的方法, 在OC中, 方法声明如下:
- (BOOL)removeItemAtURL:(NSURL *)URL
error:(NSError **)error;
而在swift中, 声明如下:
func removeItem(at: URL) throws
可以看到, swift中的函数是没有返回值的, 也没有error参数, 还多了throws声明.
具体转换规则如下:
如果OC方法的最后一个非block类型的参数, 是NSError**, swift就能将其转换为throwing函数
如果OC的error参数是它的第一个参数, swift会尝试移除WithError 或AndReturnError后缀 来简化方法名字
如果OC方法返回一个BOOL值来表明成功或失败,swift会改变返回值类型为Void
如果OC方法返回nil来表示方法调用失败, swift会改变返回值为nonoptional类型
如果转换方式, 推断不出来, 则默认保留方法名的左边部分
注意: 可以对OC方法添加宏NS_SWIFT_NOTHROW, 来表示阻止Swift转换为throwing函数
只能在OC中处理异常
在OC中, 异常与错误是明显不同的
异常: OC的异常处理使用@try、@catch、@throw语法来表明不可恢复的程序错误
错误: OC是用NSError来表示一个可以恢复的错误
在Swift中, 错误可以进行处理来恢复程序运行, 而对OC的异常, 则没有安全的方法来恢复, 所要处理OC中的异常, 只能用OC来处理后, 再在用Swift调用
延迟执行
使用关键字defer可以将一系列语句, 延迟到当前代码块结束时执行, 而不用关心具体在代码块中的位置, 一般用来做一些必要的清除操作.
func defer1() {
var c = 0
print(c)
c += 1
print(c)
defer {
c += 2
print("defer", c)
}
c += 1
print(c)
}
输出结果:
0
1
2
defer 4
可以看出: defer的执行时机,是在当前代码块的}之前, 且defer在执行前并不会捕获代码块中的变量
如果有多个defer, defer的执行会按照添加的反序执行, 即先添加的后执行, 如:
func deferN() {
var c = 0
print(c)
c += 1
print(c)
defer {
c += 2
print("defer1", c)
}
c += 1
print(c)
defer {
c += 3
print("defer2", c)
}
defer {
c += 5
print("defer3", c)
}
}
输出结果:
0
1
2
defer3 7
defer2 10
defer1 12
函数类型
defer执行时机
无返回值
在}之前执行
有返回值
在return之后, }之前执行
throwing
在throw之后, }之前执行
总结
表示错误: 使用遵守Error协议的类型, 通常多用枚举
传递错误: 使用throws关键字, 声明函数为throwing函数
抛出错误: 使用throw关键字, 来执行抛出错误的操作
转换错误: 使用try?关键字, 有错误,表达式则为nil,并对返回值进行可选类型封装
断言错误: 使用try!关键字, 有错误,则中断程序, 有危险, 慎用!
捕获错误: 使用try + do-catch关键字, 有错误, 程序跳转到catch; 无错误, 则继续执行do语句块
异常处理: 对于OC中的异常, 只能用OC语言来处理
延迟执行: 使用defer关键字, 延迟执行
难点: try?、try!、try, do-catch的区别
* try?: 尝试性的去做: 不管成功与否,都会正常执行下去, 不会打断程序执行流程, 有错误, 表达式就返回nil而已
* try!: 确定性的去做: 确定可以成功, 如果不成功, 则说明有问题, 报运行错误,程序终止
* try + do-catch: 负责任的去做:成功,则正常顺序执行;不成功, 对错误进行处理
* 注意: do-catch中也可以使用try?和try!但这样做毫无意义, 因为try?和try!实际上已经对错误进行了处理, 所以catch分支压根都不会被调用,也就相当于do{}, 只是加了一个内部作用域