Swift 中的异常处理(附带与OC的对比)

4,374 阅读5分钟

1.用assert和precondition的方式来抛出错误信息

assert(断言)

断言机制:不符合指定条件就抛出运行时错误,常用于Debug阶段的条件判断

Swift中的assert的使用与OC中的NSAssert类似

oc中的用法
- (void)assertfunction:(NSString *)email password :(NSString *)password {
    NSAssert(email.length < 8, @"invalid  email");
    NSAssert(password.length < 8, @"invalid password");
}

swift中的用法
func assertfunction(email: String ,password: String) {
    assert(email.count < 8, "invalid email")
    assert(password.count < 8, "invalid password")
}

precondition

precondition为Swift独有(OC没有),使用方式与assert一样,第一个参数填写判断的条件第二个是message.Swift中precondition的作用与assert类似,最大的区别在于assert只能在debug模式下使用,而precondition在debug模式和Release模式下都能够起作用.

precondition(condition: Bool, message: String)

2. 使用try catch处理异常

先看一下OC中的try catch

- (void)trycatchfunction {
    @try {
        NSArray<NSString*> *list = @[];
        NSLog(@"list[1]===%@",list[1]);
    } @catch (NSException *exception) {
        NSLog(@"%@",exception.description);
    } @finally {
        
    }
}

oc中的try catch,会将try中的异常捕获,然后会执行catch中的代码,Swift中的try catch步骤要多一些分为三步

1. 自定义错误

Swift可以通过Error协议自定义运行时的错误信息,这里我们使用枚举来举例

enum RequestError :Error {//注意Swift的所有异常类型都继承于Error。
    case netError
    case serviceError
    case missingParameter(parameter: String)
    case isnil
}

2. 抛出自定义Error

func throwErrors(type :Int) throws -> String {//(在可能会抛出异常的函数声明时必须加上关键字throws)
    if type == 1 {
        throw RequestError.netError (//函数内部通过throw来抛出自定义Error)
    }else if type == 2 {
        throw RequestError.serviceError
    }else if type == 3 {
        throw RequestError.missingParameter(parameter: "password")
    }
    return "success"
}

3.捕获异常(catch)

do {
    let errorMsg = try throwErrors(type: 2) //当调用可能会抛出Error的函数时需要使用关键字try

    //不抛出Error会继续执行下面的代码,如果抛出Error后,try下一句直到作用域结束的代码都将停止运行
    print(errorMsg)
    // let array : [Int] = []
    // 如果执行 print(array[2])程序会崩溃,swift中的try catch 只能捕获事先定义好的异常,这点与OC有很大不同
} catch  {
    //报错则执行相对应的错误类型
    
    switch error {//这里要特别说明一下swift的error是异常,与OC中的NSError不是一回事,与OC中的NSException相似
    case RequestError.netError :
        print("是网络错误")
    case RequestError.serviceError :
        print("是服务端错误")
    default:
        print("缺少参数")
    }
}

4.处理Error的两种方式

  1. 使用do-catch捕捉Error
func findOptionsError(value :String?) throws -> [String] {
    guard let value = value else {
        throw RequestError.isnil
    }
    return [value]
}

do {
    try findOptionsError(value: nil)
} catch let error {
    print(error)
}
  1. 不捕捉Error

let value = try findOptionsError(value: nil)
//如果有其他的函数调用这个函数
func test() throws {
    try findOptionsError(value: nil)//这里的Error会自动抛给上层函数,如果层层上报,一直到main函数,依然没有捕捉Error,那么程序就会Crash
}

let value = try? findOptionsError(value: nil)//此处异常会返回一个nil


let crash = try! findOptionsError(value: nil)//此处会崩溃

try try? try!的区别

try : 执行函数后,如果有异常需要catch异常,如果不catch,则会抛给上层函数,如果最终还是没有处理则程序crash

try? :是可选性的执行,不报错的时候返回正常的值.如果有异常会返回一个nil,程序不会crash.

try! :是强制解包,当抛出异常的时候也解包,导致崩溃问题。

rethrows

rethrows表明函数本身并不会抛出错误,但是调用它的闭包参数(函数参数)会抛出错误,那么他会将错误上抛.我们写一个类似于系统的faltMap函数来举例.


func transform( _ num :Int) throws -> String {
    return "\(num)"
}
func flatMap (array :[Int]) throws -> [String] {
    var result :[String] = []
    for element in array {
       result.append( try transform(element))
    }
    return result
}
flatMap这个函数本身是不会抛出错误的,但是因为调用了transform 这个可能抛出错误的函数,所以需要使用 throws,将错误上报 ,接下来我们把transform函数变成faltMap的一个闭包参数,将这两个函数合并成一个函数

func flatMap(array : [Int] ,_ transform : (Int) throws -> String) throws  -> [String] {
    var result :[String] = []
    
    for element in array {
        result.append( try transform(element))
    }
    return result
}

_ = flatMap(array: [1,2,3]) { (num) -> String in
    return "\(num)"
}
这样调用新的flatMap和调用之前的faltMap达到的效果就一致了.接下来我们再做一点改动 将最后的throws换成 rethrows

func flatMap(array : [Int] ,_ transform : (Int) throws -> String) rethrows  -> [String] {
    var result :[String] = []
    
    for element in array {
        result.append( try transform(element))
    }
    return result
}

在这里将throws 换成rethrows有两个好处

  1. 告诉调用者函数的参数中有可能会抛出错误的闭包参数.
  2. 将throws替换成rethrows后,调用这个函数时不需要再使用关键字try,可以直接调用

5.defer语句

defer语句:用来定义以任何方式(抛错误,return等)离开代码前必须要执行的代码 defer语句将延迟至当前作用域结束之前执行

func judgeValuable( _ passWord :String?) throws {
    guard let passWord = passWord else {
        throw RequestError.isnil
    }
    print("passWord is ",passWord)
}

func saveThePassword(_ passWord:String?) throws {
    defer {
        print("已经检验过密码")
    }
    try? judgeValuable(passWord)
}

try saveThePassword(nil)
//isnil
//已经检验过密码

defer语句的执行顺序与定义顺序相反

func fn1() {print("fn1") }
func fn2() {print("fn2") }
func fn3() {print("fn3") }

func testDefer() {
    defer {
        fn1()
    }
    defer {
        fn2()
    }
    defer {
        fn3()
    }
}
testDefer()
//fn3
//fn2
//fn1

3. fatalError

如果遇到严重的问题时,希望结束程序运行,可以直接使用fatalError函数抛出错误(这种错误无法通过do catch捕获),一旦调用程序就会终止.

func pay(amount :Int) -> Bool {
    if amount > 0 && amount < 10000 {
        return true
    }
    fatalError("金额超出范围")
}

pay(amount: 100000)//程序会直接终止

相关的swift 代码在 这里.如果有疑问可以看一下.

iOS的报错和捕获异常先说这么多,如果有错误希望大家指出.欢迎评论.