Swift 錯誤處理和調(diào)試詳解

1.表示和拋出錯誤

在Swift中,錯誤由符合Error協(xié)議的類型的值表示。這個空協(xié)議表示類型可以用于錯誤處理。
Swift枚舉特別適合于對一組相關(guān)錯誤條件進行建模,還可以給相關(guān)值添加錯誤性質(zhì)的附加信息。

enum VendingMachineError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}

使用關(guān)鍵字throw表示,待執(zhí)行語句拋出的錯誤信息。

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

2.處理錯誤

(1).使用拋出函數(shù)傳播錯誤

  • 使用關(guān)鍵字throws表示函數(shù),方法,或構(gòu)造器可以拋出錯誤;
  • throws添加在函數(shù)聲明的參數(shù)后面,返回箭頭->之前;
  • 拋出函數(shù)在執(zhí)行是,將其中拋出的錯誤傳播到調(diào)用它的范圍;
func canThrowErrors() throws -> String

注意:只有拋出函數(shù)才能傳播錯誤。非throwing函數(shù)拋出的任何錯誤,必須在函數(shù)內(nèi)部處理;

struct Item {
    var price: Int
    var count: Int
}

class VendingMachine {
    var inventory = [
        "Candy Bar": Item(price: 12, count: 7),
        "Chips": Item(price: 10, count: 4),
        "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0

    func vend(itemNamed name: String) throws {
        guard let item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }

        guard item.count > 0 else {
            throw VendingMachineError.outOfStock
        }

        guard item.price <= coinsDeposited else {
            throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }

        coinsDeposited -= item.price

        var newItem = item
        newItem.count -= 1
        inventory[name] = newItem

        print("Dispensing \(name)")
    }
}

vend(itemNamed:方法內(nèi)部并沒有處理錯誤,而是直接將拋出的錯誤傳播出去。其他調(diào)用該方法的代碼必需使用do-catch語句,try?,try!,或繼續(xù)傳播錯誤等方式處理這些錯誤。

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}

buyFavoriteSnack(person: vendingMachine:)也是
一個拋出函數(shù),它內(nèi)部調(diào)用的vend(itemNamed:方法會拋出錯誤,因此在方法執(zhí)行前面添加關(guān)鍵字try來調(diào)用。

如拋出函數(shù)一樣,拋出構(gòu)造器也可以按同樣的方式傳播錯誤。

struct PurchasedSnack {
    let name: String
    init(name: String, vendingMachine: VendingMachine) throws {
        try vendingMachine.vend(itemNamed: name)
        self.name = name
    }
}

(2).使用 Do-Catch處理錯誤
通過運行代碼塊,可以使用do-catch語句來處理錯誤。如果do閉包中的代碼拋出了錯誤,它將與catch閉包匹配,以確定哪一個可以處理錯誤。

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
} catch {
    statements
}

可以在catch之后編寫要處理的錯誤模式,以指示閉包可以處理哪些錯誤。如果catch閉包沒有相匹配的錯誤模式,則將錯誤交給最后的catch閉包來處理,并將錯誤綁定到名為error的本地常量中。

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
    print("Success! Yum.")
} catch VendingMachineError.invalidSelection {
    print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
    print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
    print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
} catch {
    print("Unexpected error: \(error).")
}
// Prints "Insufficient funds. Please insert an additional 2 coins."

在上面的例子中,buyFavoriteSnack(person:vendingMachine:)函數(shù)在try表達式中調(diào)用,因為他會拋出錯誤。當(dāng)有錯誤拋出時,就立刻轉(zhuǎn)到相應(yīng)的catch閉包中處理錯誤。若沒有錯誤,則繼續(xù)執(zhí)行do閉包中的剩下語句。

這些catch閉包不必處理do閉包中的代碼可能拋出的所有錯誤。如果沒有catch閉包處理錯誤,則錯誤將傳播到周圍的范圍。但是,傳播的錯誤必須由周圍的某個范圍處理。在非拋出函數(shù)中,包含do-catch的閉包必須處理錯誤。在拋出函數(shù)中,要么包含do-catch閉包,要么調(diào)用方必須處理錯誤。如果錯誤傳播到頂級范圍而不被處理,將出現(xiàn)運行時錯誤。

func nourish(with item: String) throws {
    do {
        try vendingMachine.vend(itemNamed: item)
    } catch is VendingMachineError {
        print("Invalid selection, out of stock, or not enough money.")
    }
}

do {
    try nourish(with: "Beet-Flavored Chips")
} catch {
    print("Unexpected non-vending-machine-related error: \(error)")
}
// Prints "Invalid selection, out of stock, or not enough money."

(3).將錯誤轉(zhuǎn)換為可選值

可以使用try?通過將錯誤轉(zhuǎn)換為可選值來處理錯誤。如果在執(zhí)行try?表達式時拋出錯誤,則表達式的值將為nil。

func someThrowingFunction() throws -> Int {
    // ...
}

let x = try? someThrowingFunction()

let y: Int?
do {
    y = try someThrowingFunction()
} catch {
    y = nil
}

當(dāng)你想用同樣的方式來處理所有的錯誤時,使用try?可以讓你寫出簡潔的錯誤處理代碼。
若下例所示,如果所有方法都失敗,則返回nil:

func fetchData() -> Data? {
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}

(4).禁用錯誤傳播
有時你知道拋出函數(shù)或方法不會在運行時拋出錯誤。在這種情況下,你可以在表達式前面添加try!來禁止錯誤的傳播,并將調(diào)用包裝在運行時斷言中,斷言不會拋出錯誤。
如果實際拋出了一個錯誤,將得到一個運行時錯誤。

let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

3.指定清理操作

  • defer語句所在的代碼塊,無論是以何種方式結(jié)束執(zhí)行(如拋出錯誤,break或return語句), defer中的代碼始終都會被執(zhí)行,且最后執(zhí)行;
  • 當(dāng)多個defer語句在同一代碼塊中時,它被執(zhí)行的順序與其在源代碼中寫入的順序相反(即源代碼順序中的最后一個defer語句首先執(zhí)行);
    例如,使用defer語句來確保關(guān)閉了文件描述符并釋放手動分配的內(nèi)存。
func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // Work with the file.
        }
        // close(file) is called here, at the end of the scope.
    }
}

4.斷言和先決條件

  • 斷言和先決條件是在運行時發(fā)生的檢查。
  • 如果斷言或先決條件中的布爾值為true,則代碼將繼續(xù)正常執(zhí)行。如果為false,則程序的當(dāng)前狀態(tài)無效; 代碼執(zhí)行結(jié)束,應(yīng)用程序終止。
  • 斷言可以在開發(fā)過程中發(fā)現(xiàn)錯誤和錯誤的假設(shè);先決條件可以檢測生產(chǎn)中的問題。
  • 斷言和先決條件與上述錯誤處理中討論的錯誤條件不同,它們不用于可恢復(fù)的或預(yù)期的錯誤。因為失敗的斷言或先決條件表示無效的程序狀態(tài),所以無法捕獲失敗的斷言。
  • 使用斷言和先決條件執(zhí)行有效的數(shù)據(jù)和狀態(tài),可以使應(yīng)用程序在出現(xiàn)無效狀態(tài)時更容易終止,易于調(diào)試問題。
  • 斷言和先決條件之間的區(qū)別在于何時檢測它們:斷言只在調(diào)試版本中檢查,而先決條件在調(diào)試和生產(chǎn)版本中都檢查。在生產(chǎn)版本中,不進行評估斷言中的條件。這意味著可以在開發(fā)過程中使用任意數(shù)量的斷言,而不會影響生產(chǎn)中的性能。

(1).使用斷言調(diào)試
從Swift標(biāo)準(zhǔn)庫中調(diào)用assert(_:_:file:line:)函數(shù)來編寫斷言。向此函數(shù)傳遞給一個表達式,該表達式求值為truefalse。如果條件的結(jié)果為false,則顯示一條消息。
例如:

let age = -3
assert(age >= 0, "A person's age can't be less than zero.")
// This assertion fails because -3 is not >= 0.

可以省略斷言消息 - 例如:

assert(age >= 0)

若代碼已經(jīng)檢查了條件,則使用assertionFailure(_:file:line:)函數(shù)來指明斷言已失敗。例如:

if age > 10 {
    print("You can ride the roller-coaster or the ferris wheel.")
} else if age > 0 {
    print("You can ride the ferris wheel.")
} else {
    assertionFailure("A person's age can't be less than zero.")
}

(2).執(zhí)行先決條件
當(dāng)條件可能為假時,但是為了能夠使代碼繼續(xù)執(zhí)行,條件必須為真,則使用先決條件。
可以通過調(diào)用precondition(_:_:file:line:)函數(shù)來編寫先決條件。向該函數(shù)傳遞一個計算結(jié)果為真或假的表達式,并在條件結(jié)果為假時顯示一條消息。例如:

// In the implementation of a subscript...
precondition(index > 0, "Index must be greater than zero.")

還可以調(diào)用preconditionFailure(_:file:line:)函數(shù)來指明失敗發(fā)生時——例如,如果選擇了開關(guān)的默認(rèn)情況,但是所有有效的輸入數(shù)據(jù)應(yīng)該由開關(guān)的其中一種情況處理。

5.致命錯誤

  • 無條件輸出給定的錯誤消息并停止執(zhí)行。
//message: 要打印的字符串
//file: 輸出“message”的文件名
//line:輸出“message”的行號
public func fatalError(_ message: @autoclosure () -> String = default, file: StaticString = #file, line: UInt = #line) -> Never

6.其他專題模塊

Swift 4.2 基礎(chǔ)專題詳解

最后編輯于
?著作權(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)容

  • ??由于 JavaScript 本身是動態(tài)語言,而且多年來一直沒有固定的開發(fā)工具,因此人們普遍認(rèn)為它是一種最難于調(diào)...
    霜天曉閱讀 823評論 0 1
  • 本章將會介紹 自動引用計數(shù)的工作機制自動引用計數(shù)實踐類實例之間的循環(huán)強引用解決實例之間的循環(huán)強引用閉包引起的循環(huán)強...
    寒橋閱讀 1,029評論 0 0
  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,546評論 0 13
  • 初夏將致 頂著火紅的烈日 迎著曬脫皮的臉頰 也要在強光下苦練車技 只為早日拿到車牌 一路風(fēng)光無限 蟬鳴低唱 云霧縈...
    婉_霞閱讀 550評論 7 19
  • 新春的鐘聲已經(jīng)敲響,新年的祝福送到四面八方。 外面鞭炮聲聲,預(yù)示新的一年紅紅火火蒸蒸日上。 過年是團圓的日子,一家...
    萬兒閱讀 301評論 0 3

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