Swift進(jìn)階(十二)錯誤處理Error

錯誤的類型

  • 開發(fā)過程中常見的錯誤:
    語法錯誤(編譯報錯)
    邏輯錯誤(一般跟業(yè)務(wù)相關(guān))
    運行時錯誤(可能會導(dǎo)致閃退,一般也叫異常)
    ......

自定義錯誤

  • Swift中可以通過Error協(xié)議自定義運行時的錯誤信息
enum SomeError: Error {
    case illegalArg(String)
    case outOfBounds(Int, Int)
    case outOfMemory
}
  • 函數(shù)內(nèi)部通過throw拋出自定義Error,可能會拋出Error的函數(shù)必須加上throws聲明
func divide(_ num1: Int, _ num2: Int) throws -> Int {
    if num2 == 0 {
        throw SomeError.illegalArg("0不能作為除數(shù)")
    }
    return num1 / num2
}
  • 需要使用try調(diào)用可能會拋出Error的函數(shù)
var result = try divide(20, 0)

處理Error

  • 處理Error的兩種方式
    ① 通過do-catch捕捉Error
    ② 不捕捉Error,在當(dāng)前函數(shù)增加throws聲明,Error將自動拋給上層函數(shù);如果最頂層函數(shù)(main函數(shù))依然沒有捕捉Error,那么程序?qū)⒔K止

方法①:do-catch

  • 可以使用do-catch捕捉Error
func test() {
    print("1")
    do {
        print("2")
        _ = try divide(20, 0)
        print("3")
    } catch let SomeError.illegalArg(msg) {
        print("參數(shù)異常", msg)
    } catch let SomeError.outOfBounds(size, index) {
        print("下標(biāo)越界:", "size=\(size)", "index=\(index)")
    } catch SomeError.outOfMemory {
        print("內(nèi)存溢出")
    } catch {
        print("其他錯誤")
    }
    print("4")
}

test()
/*輸出結(jié)果*/
1
2
參數(shù)異常 0不能作為除數(shù)
4
  • 注意:拋出Error后,try下一句直到作用域結(jié)束的代碼都將停止運行
    當(dāng)然我們上面的do-catch還可以這樣寫:
do {
    _ = try divide(20, 0)
} catch let error {
    switch error {
    case let SomeError.illegalArg(msg):
        print("參數(shù)異常", msg)
    default:
        print("其他異常")
    }
}
  • 這里錯誤判斷,我們也可以先判斷大類,再細(xì)分具體的錯誤
do {
    _ = try divide(20, 0)
} catch let err where err is SomeError { ///先區(qū)分error類型,再區(qū)分具體的錯誤內(nèi)容
    print(err)
}
  • 注意:只區(qū)分錯誤類型的話,只用is SomeError就可以,但是這種寫法,在當(dāng)前catch作用域內(nèi),就無法去細(xì)分error了:
    image.png

方法②:不捕捉,將Error拋給上層函數(shù)

func test() throws {
    print("1")
    print(try divide(20, 0))
    print("2")
}
try test()
/*輸出結(jié)果*/
1
Fatal error: Error raised at top level:......
  • 語法糖:如果結(jié)合throws,我們還可以在test函數(shù)里面這樣寫(注意:如果test沒有聲明throws這樣寫會報錯的):
func test() throws {
    print("1")
    do {
        print("2")
        _ = try divide(20, 0)
        print("3")
    } catch let error as SomeError {
        print(error)
    }
    print("4")
}

try test()
/*輸出結(jié)果*/
1
2
illegalArg("0不能作為除數(shù)")
4

try?、try!

  • 可以使用try?、try!調(diào)用可能會拋出Error的函數(shù),這樣就不用去處理Error
func test() {
    print("1")
    var result1 = try? divide(20, 10) // Optional(2), Int?
    var result2 = try? divide(20, 0) // nil, Int?
    var result3 = try! divide(20, 10) // 2, Int
    print("2")
}
test()
  • 下面兩段代碼是等價的,利用do-catch可以做到和try?一樣的效果:
var a = try? divide(20, 0)
var b: Int?
do {
    b = try divide(20, 0)
} catch{
    b = nil
}
/************ 或者這樣寫也行 ***************/
var b: Int?
do {
    b = try divide(20, 0)
} catch{}

rethrows

  • rethrows表明:函數(shù)本身不會拋出錯誤,但調(diào)用閉包參數(shù)(或者傳入的函數(shù))拋出錯誤,那么它會將錯誤向上拋
  • 這里rethrows僅僅是一個聲明,換成throws也可以,只是更加精確
func exc(_ fn: (Int, Int) throws -> Int, _ num1: Int, _ num2: Int) rethrows {
    print(try fn(num1, num2))
}
// Fatal error: Error raised at top level
try exc(divide(_:_:), 20, 0)

我們也可以參考一下??(空合并運算符)

image.png

defer

  • defer語句:用來定義以任何方式(拋錯誤、return等)離開代碼前必須要執(zhí)行的代碼
  • defer語句將延遲至當(dāng)前作用域結(jié)束前執(zhí)行

我們來看下面的一段代碼:

func open(_ filename: String) -> Int {
    print("openFile")
    return 0
}

func close(_ file: Int) {
    print("closeFile")
}

接下來我利用上面的兩個函數(shù)對文件做一些操作

func processFile(_ filename: String) throws {
    let file = open(filename)
    
    // 使用file的相關(guān)操作
    // ...此處省略一千行代碼
    try divide(20, 0) // 中間有一個代碼可能會拋出錯誤
    
    close(file)
}

do {
    try processFile("test.txt")
} catch {}

/*打印結(jié)果*/
openFile

可以看到這種情況下,文件不會被關(guān)閉。在真是的開發(fā)環(huán)境中,會造成內(nèi)存泄漏,等等一些問題。

這個時候,我們就可以利用defer來避免這個問題:

func processFile(_ filename: String) throws {
    let file = open(filename)
    defer {
        close(file)
    }
    
    // 使用file的相關(guān)操作
    // ...此處省略一千行代碼
    try divide(20, 0) // 中間有一個代碼可能會拋出錯誤
    
}

do {
    try processFile("test.txt")
} catch {}

/*打印結(jié)果*/
openFile
closeFile

我們在open的時候,就直接利用defer語句把close函數(shù)code上;這樣,不管后面的代碼是否拋出異常、還是正常退出,文件都會被關(guān)掉。

  • 還有一點要注意:defer語句的執(zhí)行順序和定義順序是相反的:
func fn1() {
    print("fn1")
}
func fn2() {
    print("fn2")
}
func test() {
    defer {
        fn1()
    }
    defer {
        fn2()
    }
}

test()
/*輸出結(jié)果*/
fn2
fn1

assert(斷言)

  • 很多編程語言都有斷言機(jī)制:不符合指定條件就拋出運行時錯誤,長用于調(diào)試(Debug)階段的條件判斷`
  • 默認(rèn)情況下,Swift的斷言只會在Debug模式下生效,Release模式下會忽略
func divide(_ v1: Int, _ v2: Int) -> Int {
    assert(v2 != 0, "除數(shù)不能為0")
    return v1 / v2
}
print(divide(20, 0))
  • 增加Swift Flags修改斷言的默認(rèn)行為
  • -assert-config Release:強(qiáng)制關(guān)閉斷言
  • -assert-config Debug:強(qiáng)制開啟斷言
    image.png

fatalError

  • 如果遇到嚴(yán)重問題,希望結(jié)束程序運行時,可以直接使用fatalError函數(shù)拋出錯誤(這是無法通過do-catch捕捉的錯誤)
  • 使用了fatalError函數(shù),就不需要再寫return
func test(_ num: Int) -> Int {
    if num >= 0 {
        return 1
    }
    fatalError("num不能小于0")
}
  • 在某些不得不實現(xiàn)、但不希望別人調(diào)用的方法,可以考慮內(nèi)部使用fatalError函數(shù)
    image.png

局部作用域

  • 可以使用do實現(xiàn)局部作用域
do {
    let dog1 = Dog()
    dog1.age = 10
    dog1.run()
}

do {
    let dog2 = Dog()
    dog2.age = 10
    dog2.run()
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 錯誤處理(Error Handling) 錯誤類型 語法錯誤(編譯錯誤) 邏輯錯誤 運行時錯誤(可能會導(dǎo)致閃退,一...
    iVikings閱讀 1,061評論 0 1
  • 一、錯誤處理 1.1、錯誤類型語法錯誤(編譯報錯)邏輯錯誤運行時錯誤(可能會導(dǎo)致閃退,一般也叫做異常) 1.2、自...
    IIronMan閱讀 695評論 0 3
  • 錯誤處理也就是異常處理 錯誤類型 開發(fā)過程常見的錯誤 語法錯誤(編譯報錯) 邏輯錯誤 運行時錯誤(可能會導(dǎo)致閃退,...
    codeTao閱讀 150評論 0 1
  • 一. 錯誤處理 開發(fā)中常見的錯誤: 語法錯誤(編譯時會報錯) 邏輯錯誤 運行時錯誤(可能會導(dǎo)致閃退,一般也叫做異常...
    Imkata閱讀 882評論 0 0
  • 開發(fā)過程常見的錯誤 語法錯誤(編譯報錯) 邏輯錯誤 運行時錯誤(可能會導(dǎo)致閃退,一般也叫做異常) 1.自定義錯誤 ...
    happy神悅閱讀 389評論 0 1

友情鏈接更多精彩內(nèi)容