【Swift 3.1】18 - 錯(cuò)誤處理 (Error Handling)

【Swift 3.1】18 - 錯(cuò)誤處理 (Error Handling)

自從蘋果2014年發(fā)布Swift,到現(xiàn)在已經(jīng)兩年多了,而Swift也來(lái)到了3.1版本。去年利用工作之余,共花了兩個(gè)多月的時(shí)間把官方的Swift編程指南看完。現(xiàn)在整理一下筆記,回顧一下以前的知識(shí)。有需要的同學(xué)可以去看官方文檔>>


錯(cuò)誤處理是在程序中響應(yīng)錯(cuò)誤條件和恢復(fù)錯(cuò)誤條件的過(guò)程。

表示和拋出錯(cuò)誤 (Representing and Throwing Errors)

在Swift中,錯(cuò)誤是用遵循了Error協(xié)議的類型的值來(lái)表示。枚舉非常適合用來(lái)封裝相關(guān)的錯(cuò)誤。

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

是用throw來(lái)拋出錯(cuò)誤:

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

處理錯(cuò)誤 (Handling Errors)

當(dāng)錯(cuò)誤拋出后,一些相關(guān)的代碼必須處理錯(cuò)誤,例如改正錯(cuò)誤、嘗試另外一種辦法或者告知用戶有錯(cuò)誤。

在Swift中,有四種方法來(lái)處理錯(cuò)誤。把錯(cuò)誤傳遞給調(diào)用這個(gè)方法的代碼;使用do-catch語(yǔ)句處理錯(cuò)誤;把錯(cuò)誤處理為一個(gè)可選類型的值;或者斷言這個(gè)錯(cuò)誤不會(huì)發(fā)生。下面會(huì)演示這四個(gè)方法。

當(dāng)一個(gè)方法拋出了錯(cuò)誤,它會(huì)改變程序的流程,所以及時(shí)發(fā)現(xiàn)錯(cuò)誤的位置非常重要。為了發(fā)現(xiàn)錯(cuò)誤的位置,在調(diào)用方法或初始化器的代碼前使用trytry?或者try!。

使用拋出方法來(lái)傳遞錯(cuò)誤 (Propagating Errors Using Throwing Functions)

為了表示一個(gè)方法或初始化器可以拋出異常,在方法參數(shù)后面加上throw關(guān)鍵字:

func canThrowErrors() throws -> String
func cannotThrowErrors() -> String

注意:只有拋出方法才能傳遞錯(cuò)誤,不能拋出錯(cuò)誤的方法只能在方法內(nèi)處理錯(cuò)誤。

下面是一個(gè)例子:

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:)方法的實(shí)現(xiàn)中,使用guard語(yǔ)句來(lái)拋出錯(cuò)誤。

因?yàn)?code>vend(itemNamed:)把錯(cuò)誤傳出去了,所以調(diào)用這個(gè)方法的代碼必須處理錯(cuò)誤:使用do-catch語(yǔ)句、try?、try!或者繼續(xù)把錯(cuò)誤傳出去。下面演示的是用繼續(xù)把錯(cuò)誤傳出去的方法來(lái)處理:

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)
}

因?yàn)?code>vend(itemNamed:)能拋出錯(cuò)誤,所以能再前面加上try。

初始化器也能拋出錯(cuò)誤:

struct PurchasedSnack {
    let name: String
    init(name: String, vendingMachine: VendingMachine) throws {
        try vendingMachine.vend(itemNamed: name)
        self.name = name
    }
}
使用Do-Catch來(lái)處理錯(cuò)誤 (Handling Errors Using Do-Catch)

do-catch語(yǔ)句的通用形式:

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

catch后面寫一個(gè)樣式來(lái)提示什么樣的錯(cuò)誤能被這個(gè)catch語(yǔ)句處理。如果catch語(yǔ)句沒(méi)有樣式,那么這個(gè)語(yǔ)句會(huì)匹配任何錯(cuò)誤,并且綁定這個(gè)錯(cuò)誤作為一個(gè)本地常量error。

catch語(yǔ)句不必處理每一個(gè)可能的錯(cuò)誤。如果沒(méi)有catch語(yǔ)句處理這個(gè)錯(cuò)誤,那么這個(gè)錯(cuò)誤將會(huì)傳遞給周圍——要么用do-catch語(yǔ)句處理,要么通過(guò)內(nèi)部的拋出方法。例如下面這個(gè)例子除了VendingMachineError的所有枚舉值,但其他錯(cuò)誤只能由周圍的代碼去處理:

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} 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.")
}
// Prints "Insufficient funds. Please insert an additional 2 coins."
把錯(cuò)誤轉(zhuǎn)換為可選值 (Converting Error to Optional Values)

使用try?把錯(cuò)誤轉(zhuǎn)為可選值。在使用try?的語(yǔ)句中,如果有錯(cuò)誤拋出,那么這個(gè)語(yǔ)句的值為nil。

例如下面這個(gè)例子,x和y的值相同:

func someThrowingFunction() throws -> Int {
    // ...
}
 
let x = try? someThrowingFunction()
 
let y: Int?
do {
    y = try someThrowingFunction()
} catch {
    y = nil
}

當(dāng)用同一個(gè)方法來(lái)處理所有錯(cuò)誤時(shí),使用try?能是代碼更簡(jiǎn)潔:

func fetchData() -> Data? {
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}
禁用錯(cuò)誤傳遞 (Disabling Error Propagation)

有時(shí)候我們知道一個(gè)能拋出錯(cuò)誤的方法在運(yùn)行過(guò)程中時(shí)間上不會(huì)拋出錯(cuò)誤。在這種情況下,我們可以在語(yǔ)句前使用try!來(lái)禁用錯(cuò)誤傳遞,并且可以封裝在斷言內(nèi),如果真的有錯(cuò)誤拋出,那么程序報(bào)運(yùn)行時(shí)錯(cuò)誤。

例如:

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

指定清理操作 (Specifying Cleanup Actions)

使用defer語(yǔ)句在代碼執(zhí)行離開當(dāng)前代碼塊之前執(zhí)行一些語(yǔ)句。不管代碼執(zhí)行如何離開當(dāng)前代碼塊,不管是因?yàn)閳?bào)錯(cuò)、return或者break,defer中的語(yǔ)句都能讓我們做一些必要的清理。例如,可以使用defer語(yǔ)句來(lái)保證文件描述符被關(guān)閉和手動(dòng)分配的內(nèi)存被釋放。

defer語(yǔ)句直到當(dāng)前代碼塊退出時(shí)才會(huì)執(zhí)行。不能包括轉(zhuǎn)移控制語(yǔ)句,例如break或者return,或者拋出一個(gè)錯(cuò)誤。延遲操作將按照他們定義的順序的反序執(zhí)行,也就是說(shuō),第一個(gè)defer語(yǔ)句在第二個(gè)defer語(yǔ)句之后執(zhí)行。

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.
    }
}

上面這個(gè)例子使用defer語(yǔ)句來(lái)保證open(_:)方法有對(duì)應(yīng)的close(_:)方法。

注意:即使沒(méi)有涉及錯(cuò)誤處理代碼也可以使用defer語(yǔ)句。


第十八部分完。下個(gè)部分:【Swift 3.1】19 - 類型轉(zhuǎn)換 (Type Casting)


如果有錯(cuò)誤的地方,歡迎指正!謝謝!

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

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

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