为了减少程序运行中产生异常,就需要一套异常处理机制
那些常规错误,例如网络请求错误。这些错误,在编写代码的时候尽可能去避免就好。那些预料之外的错误,比如,数组越界,range边界,除数不能是0等,就要用到异常处理机制。
在swift中在发生异常的位置添加如下几种代码,可以通过下面几种方式处理错误:
assert
、precondition
、fatalError
.
assert 只在Debug环境下生效
precondition 在Debug和生产阶段都都生效
fatalError 抛出异常,无法捕获
这几种方式发生异常都能定位到具体的行,缺点是不能再恢复执行(也就是闪退)
swift的错误处理机制
Error协议
Error协议本身没有内容,遵守这个Error协议,就可以将错误抛出和捕获,而且任何自定义的类型都可以遵守此Error协议。
看一个官方文档中的例子:
从字符串解析整数时可能发生的两种不同类型的错误:
enum IntParsingError: Error {
case overflow // 溢出
case invalidInput(Character) //包含无效的字符
}
// 扩展 Int类型 ,
extension Int {
init(validating input: String) throws {
// ...
let c = _nextCharacter(from: input)
if !_isValid(c) {
throw IntParsingError.invalidInput(c)
}
}
}
// 解析字符串,通过 do-catch
do {
let price = try Int(validating: "$100")
} catch IntParsingError.invalidInput(let invalid) {
print("Invalid character: '\(invalid)'")
} catch IntParsingError.overflow {
print("Overflow error")
} catch {
print("Other error")
}
// Prints "Invalid character: '$'"
throws
throws用来标记一个方法,该方法会在失败时抛出异常
// 无返回值
func initWithValidating(from:String) throws
// 有返回值
func initWithValidating(from:String) throws -> Int
完整的定义如下:
// 传入一个字符串,返回该字符串的Int类型
func initWithValidating(from:String) throws -> Int {
// ...
let c = _nextCharacter(from)
if !_isValid(c) {
throw IntParsingError.invalidInput(c)
}
return Int(from)
}
被标记为throws的API必须通过完整的 try catch 来捕获可能的异常,否则无法编译通过:
do{
let result = try initWithValidating(from:"$100")
// do...
}catch{
// throw抛出异常会来到这里
}
或者带有更具体的错误信息
do{
let result = try initWithValidating(from:"$100")
// do...
} catch IntParsingError.invalidInput(let invalid) {
print("Invalid character: '\(invalid)'")
} catch IntParsingError.overflow {
print("Overflow error")
} catch {
// 虽然两种情况都考虑到了,但是编译器要求必须加上默认的catch块
// 编译器自动生成的error变量
print("Other \(error)")
}
rethrows
继续上面的例子,我们现在需要对返回的整数做一下格式化,输出如下效果:
输入:10000000000
输出:10,000,000,000
下面这个函数可以对转换后的数字格式化:
func formatNum(from:String,v:(String) throws -> Int) throws {
do{
let num = try v(from)
let format = NumberFormatter()
format.numberStyle = .decimal
if let result = format.string(from: NSNumber(value: num)){
print(result)
}
}catch{
throw error
}
}
现在使用这个新的方法formatNum,参数v可以传入上面的initWithValidating
并且传入"10000000000"作为第一个参数,编译器会提示我们必须加上 try 来处理可能发生的异常,但是很明显这个数字不会发生异常。
typealias verify = (String) throws -> Int
var validating:verify = { from in
for i in from where !i.isNumber{
throw "Error:'\(i)' not a number!"
}
return Int(from)!
}
// 编译器会提示我们必须加上 try 来处理可能发生的异常
try formatNum(from: "10000000000", v: validating)
现在把formatNum后面的throws标记改成rethrows,并且传入一个不会抛出异常的参数v,这个时候编译器不再提示我们必须加上 try 来处理可能发生的异常,编译也正常通过
var noErrorValidating:(String)->Int = { from in
return Int(from)!
}
formatNum(from: "10000000000", v: noErrorValidating)
// 输出:10,000,000,000
乍一看好像没什么用,只是省略了try来捕获异常。
其实,Swift标准库提供的map函数也是这么实现的,当我map一个序列,后面传入的transform不带抛出异常的时候我们不需要前面的try
@inlinable public func map<T>(_ transform: (Character) throws -> T) rethrows -> [T]
结论:
rethrows 关键字用于本身不抛出错误,而是转发包含抛出错误的函数类型参数(闭包参数)。同时提供一个便利,仅在函数参数抛出错误时需要try关键字。
使用带异常函数
do/catch
调用一个可抛出错误的函数,编译器会迫使我们决定如何处理错误。
使用 do/catch 直接处理,可能会存在多条 catch 语句,可以用模式匹配来捕获某个特定的错误类型,见上文的例子:
错误转换try try? try!
try用于 throws 和 Optionals 之间转换
try 用在do/catch中,或者转移错误
try? 返回一个可选值,并且忽略错误信息,发生错误时返回 nil
try! 返回一个可选值并且强制解包,发生错误时会crash