Swift 异常处理,以及throws和rethrows的使用

为了减少程序运行中产生异常,就需要一套异常处理机制
那些常规错误,例如网络请求错误。这些错误,在编写代码的时候尽可能去避免就好。那些预料之外的错误,比如,数组越界,range边界,除数不能是0等,就要用到异常处理机制。
在swift中在发生异常的位置添加如下几种代码,可以通过下面几种方式处理错误:

assertpreconditionfatalError.

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(_ 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

你可能感兴趣的:(Swift 异常处理,以及throws和rethrows的使用)