Swift 异常处理(错误处理)

110 阅读4分钟

自定义错误

  • Swift中可以通过Error协议自定义运行时的错误信息
struct MyError:Error {
    var msg:String
}
enum SomeError : Error {
    case illegalArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}
  • 函数内部通过throw抛出自定义Error,可能会抛出Error的函数必须加上throws声明
func divide(_ num1:Int,_ num2:Int) throws -> Int {
    if num2 == 0 {
        throw MyErrow(msg:"0不能作为除数")
    }
    return num1 / num2
}

func divide(_ num1:Int,_ num2:Int) throws -> Int {
    if num2 == 0 {
        throw SomeError.illegalArg("0不能作为除数")
    }
    return num1 / num2
}

  • 需要使用try调用会抛出Error的函数
var result = try devide(10,0)
print(result)

do catch 捕捉错误

  • 可以使用do-catch捕捉Error
  • 抛出Error后,try下一句直到作用域结束的代码都将停止运行
func test(){
    print("1")
    do {
        print("2")
        print(try divide(20,0)) // 一旦产生异常,在do作用域 这行下面都不会执行
        print("3")
    } catch let SomeError.illegalArg(msg){
        print("参数异常:",msg)
    } catch let SomeError.outOfBounds(size,index){
        print("下标越界:","size=\(size),index="\(index)")
    } catch let outOfMemory.outOfMemory{
        print("内存溢出")
    } catch {
        print("其他错误")
    }
    print("4")
}
test()

处理Error

处理Error的两种方式

  1. 通过do-catch捕捉Error
  2. 不捕捉Error,在当前函数增加throws声明,Error将自动抛给上层函数 如果最顶层函数(main函数)依然没有捕捉Error,那么程序就会终止

image.png

enum SomeError : Error {
    case illegalArg(String)
    case outOfBounds(Int,Int)
    case outOfMemory
}
func divide(_ num1:Int,_ num2:Int) throws -> Int {
    if num2 == 0 {
        throw SomeError.illegalArg("0不能作为除数")
    }
    return num1 / num2
}
func test() throws {
    print("1")
    print(try divide(20, 0))
    print("2")
}
try test()
// try test()
// Fatal error:Error raised at top level
func test() throws {
    print("1")
    do {
        print("2")
        print(try divide(20,0))
        print("3")
    } catch let error as SomeError{
        print(error)
    } 
    print("4")
}
//1
//2
//illegalArg("0不能作为除数")
//4
func test() throws {
    print("1")
    do {
        print("2")
        print(try divide(20,0))
        print("3")
    } catch let error{
        switch error {
            case let SomeError.illegalArg(msg):print("参数错误",msg)
            default:print("其他错误")
        }
    } 
    print("4")
}
do {
    print(try divide(20,0))
} catch is SomeError {
    print("SomeError")
}

try?、try!

可以使用try?try!调用可能会抛出Error的函数,这样就不用处理Error

func test(){
    print("1")
    var result1 = try? devide(20,10) // Optional(2),Int?
    var result2 = try? devide(20,0) // nil
    var result3 = try! devide(20,10) // 2,Int
    var result4 = try! devide(20,0) // __lldb_expr_16/MyPlayground.playground:17: Fatal error: 'try!' expression unexpectedly raised an error: __lldb_expr_16.SomeError.illegalArg("0****不能作为除数** **")
    print("2")
}

a 和b 等价

var a = try? devide(20,0)

var b:Int?
do {
    b = try devide(20,0)
} catch {
    b = nil
}

rethrows

  • rethrows表明:函数本身不会抛出错误,但调用闭包参数抛出错误,那么它会将错误向上抛
func exec(_ fn:(Int,Int) throws -> Int,_ num1:Int,_ num2:Int) rethrows {
    print(try fn(num1,num2))
}
// Fatal error:Error raised at top level
try exec(divide,20,0)

defer

  • defer语句:用来定义任何方式(抛错误,return等)离开代码块钱必须要执行的代码
  • defer语句将延迟至当前作用域结束之前执行
func open(_ filename:String) -> Int {
    print("open")
    return 0
}
func close(_ file:Int){
    print("close")
}
func processFile(_ filename:String) throws {
    let file = open(filename)
    defer {
        close(file)
    }
    // 使用file
    // 。。。
    try divide(20,0)
    //close 将会在这里调用
}
try processFile("test.txt")
// open
// close
// Fatal error:Error raised at top level
  • defer语句的执行顺序与定义顺序相反
func fn1(){print("fn1")}
func fn2(){print("fn2")}
func test(){
    defer {fn1()}
     defer {fn2()}
}
//fn2
//fn1

assert(断言)

  • 很多编程语言都有断言机制:不符合指定条件就抛出运行时错误,常用于调试(Debug)阶段的条件判断
  • 默认情况下,Swift的断言只会在Debug模式下生效,Release模式下会忽略
  • 增加Swift Flags修改断言的默认行为
  • -assert-config Release:强制关闭断言
  • -assert-config Debug:强制开启断言
func divide(_ v1:Int,_ v2:Int) -> Int {
    assert(v2 != 0,"除数不能为0")
    return v1/v2
}
print(divide(20,0))

fatalError

  • 如果遇到严重问题,希望结束程序运行时,可以直接使用fatalError函数抛出错误(这是无法通过do-catch捕捉的错误)
  • 使用了fatalError函数,就不需要写return
func test(_ num:Int) -> Int {
    if num >= 0 {
        return 1
    }
    fatalError("num 不能小于0")
}
  • 在某些不得不实现,但不希望别人调用的方法,可以考虑内部使用fatalError函数
class Person {required init(){}}
class Student :Person {
    required init() {fatalError("don't call Student.init")}
    init(score:Int){}
}
var stu1 = Student(score:98)
var stu2 = Student()