21.Swift錯誤處理

/**錯誤處理:是響應錯誤以及從錯誤中恢復的過程。
    Swift提供了在運行時對可恢復錯誤的拋出、捕獲、傳遞和操作的支持。
 */

//表示并拋出錯誤
//在Swift 中 錯誤用符合Error協(xié)議的類型的值來表示。這個空協(xié)議表明該類型可以用于錯誤處理。
//枚舉類型非常適合構建一組相關的錯誤狀態(tài),枚舉的關聯(lián)值還可以提供錯誤狀態(tài)的額外信息,例如:
enum VendingMachineError: Error {//表示一個游戲中操作自動販賣機可能會出現(xiàn)的錯誤狀態(tài)
    case invalidSelection //選擇無效
    case insufficientFunds(coinsNeeded: Int) //金額不足
    case outOfStock //缺貨
}
//拋出錯誤使用 throw 關鍵字。
//throw VendingMachineError.insufficientFunds(coinsNeeded: 5) // 拋出一個錯誤提示:販賣機還需要5個硬幣


//處理錯誤
//當錯誤被拋出是,附近的代碼必須負責處理這個錯誤:糾正、嘗試另一種方式、向用戶報告錯誤等。
//為了快速識別代碼中拋出錯誤的地方,在調用一個能拋出錯誤的函數、方法或構造器之前,加上try關鍵字,或者try?、try!這種變體。
//Swift有四種處理錯誤的方式:把錯誤傳遞給調用此函數的代碼、用do-catch語句處理錯誤、將錯誤作為可選類型處理、斷言此錯誤根本不會發(fā)生。

//1.用 throwing 函數傳遞錯誤
//為了表示一個函數、方法或構造器可以拋出錯誤,在函數聲明的參數列表之后加上 throws 關鍵字、如果函數指明了返回值類型,throw關鍵字需要寫在箭頭(->)的前面;一個標有 throws 關鍵字的函數被稱為 throwing 函數
func canThrowErrors() throws -> String {
    return "可以拋出錯誤的函數"
}
func cannotThrowErrors() -> String {
    return "不能拋出錯誤的函數"
}
//注意:throwing 函數在其內部拋出錯誤,并將錯誤傳遞到函數被調用時的作用域;其他函數內部拋出的錯誤都只能在函數內部處理,不能傳遞出去。
struct ItemTestError {
    var price: Int
    var count: Int
}
class VendingMachine {
    var inventory = ["棒棒糖": ItemTestError(price: 1, count: 3),
                     "榨菜": ItemTestError(price: 9, count: 4),
                     "酸奶": ItemTestError(price: 13, count: 2)]
    var coinsDeposited = 0 //投入的硬幣
    func dispenseSnack(snack: String) {
        print("分配:\(snack)")
    }
    func vend(itemNamed name: String) throws {
        //以下使用了三條 guard 語句來提取退出方法,確保在購買中任一條件不滿足是,能提前退出方法并拋出相應的錯誤
        guard let item = inventory[name] else { // 如果選擇的商品不在 庫存中
            throw VendingMachineError.invalidSelection // 拋出錯誤: 選擇無效
        }
        guard item.count > 0 else { // 如果選擇的商品 不大于0
            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("購買2:\(name)")
    }
}

let favoriteSnacks = ["Alice": "榨菜",
                      "Bob": "酸奶",
                      "Eve": "棒棒糖"]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {//查找默認最喜歡的零食
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName) //調用該方法,來嘗試購買;因為該方法可能會拋出錯誤,所以調用時前面加上了try
}

//throwing構造器能像throwing函數一樣傳遞錯誤。
struct PurchasedSnack {//PurchasedSnack構造器在構造過程中調用了throwing函數,并且通過傳遞到它的調用者來處理這些錯誤。
    let name: String
    init(name: String, vendingMachine: VendingMachine) throws {
        try vendingMachine.vend(itemNamed: name)
        self.name = name
    }
}


//用 Do-Catch處理錯誤
//do中拋出錯誤,catch中匹配錯誤,匹配成功者做相應的處理;如果catch中沒有指定匹配模式,那么久可以匹配任何錯誤,并把錯誤綁定到一個名字為error的局部常量。
//若do中拋出的錯誤,沒有被catch匹配處理,那么就會由他周圍的作用域處理:要么是一個外圍的do-catch錯誤處理語句,要么是一個throwing函數的內部。
var vendingMachineDo = VendingMachine()
vendingMachineDo.coinsDeposited = 10
do { //do 中拋出錯誤
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachineDo)
    print("如果上一句代碼拋出了錯誤,就會跳到下方的catch中執(zhí)行,如果沒有拋出錯誤,這條語句才會被打印")
} catch VendingMachineError.invalidSelection {//拋出錯誤: 選擇無效
    print("選擇無效")
} catch VendingMachineError.outOfStock { //拋出錯誤: 缺貨
    print("缺貨")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) { //拋出錯誤:投入的硬幣不夠
    print("投入的硬幣不夠尚缺少:\(coinsNeeded)枚")
}


//將錯誤轉換成可選值
/*可以使用try?通過將錯誤轉換成一個可選值來處理錯誤。如果在評估try?表達式時一個錯誤被拋出,那么表達式的值就是nil。
func someKeFunction() throws -> Int {//如果拋出一個錯誤,那么x和y的值都是nil。否則x和y的值就是該函數的返回值。
 // ...
}
let x = try? someKeFunction()
let y: Int?
do {
    y = try someKeFunction()
} catch {
    y = nil
}
 */
//如果你想對所有的錯誤都采用同樣的方式來處理,用try?就可以讓你寫出簡潔的錯誤處理代碼。
func fetchData() -> Int? {//如果下面的代碼來獲取數據都失敗了,則返回nil
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}
func fetchDataFromDisk() throws -> Int {
    return 1
}
func fetchDataFromServer() throws -> Int {
    return 2
}

//禁用錯誤傳遞
//有時你知道某個throwing函數實際上在運行時是不會拋出錯誤的,在這種情況下,你可以在表達式前面寫try!來禁用錯誤傳遞,這回把調用包裝在一個不會有錯誤拋出的運行時斷言中。如果真的拋出了錯誤,你會得到一個運行時錯誤。
//let photo = try! loadImage(atPath:"./Resources/John Appleseed.jpg")
//上面這種情況下,因為圖片是和應用綁定的,運行時不會有錯誤拋出,所以適合禁用錯誤傳遞。



/**指定清理操作
    可以使用defer語句在即將離開當前代碼塊時執(zhí)行一系列語句。
    無論是由于拋出錯誤而離開,或是由于諸如return、break的語句而離開代碼塊,都可以用defer語句來做以前清理工作,比如:確保文件描述符得以關閉、釋放手動分配的內存等。
    defer語句由defer關鍵字和要被延遲到作用域最后執(zhí)行的語句組成,延遲執(zhí)行的語句不能包含 break語句、return語句 或 拋出一個錯誤。
    如果有多個defer語句,會按聲明的順序倒序執(zhí)行,即先聲明的最后執(zhí)行,最后一個聲明的第一個執(zhí)行。

 func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            //處理文件
        }
        //close(file) 會在這里被調用,即作用域的最后
    }
 }
 //即使沒有涉及到錯誤處理,也可以使用defer語句
 */


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

相關閱讀更多精彩內容

友情鏈接更多精彩內容