17 Error Handling 錯(cuò)誤處理

錯(cuò)誤處理是響應(yīng)程序中的錯(cuò)誤條件并從中恢復(fù)的過程。Swift為在運(yùn)行時(shí)拋出袱瓮、捕獲或油、傳播和操作可恢復(fù)錯(cuò)誤提供了一流的支持。

有些操作不能保證總是完成執(zhí)行或生成有用的輸出上鞠。選項(xiàng)用于表示沒有值际邻,但是當(dāng)操作失敗時(shí),了解導(dǎo)致失敗的原因通常很有用芍阎,這樣您的代碼就可以相應(yīng)地作出響應(yīng)世曾。

例如,考慮從磁盤上的文件中讀取和處理數(shù)據(jù)的任務(wù)谴咸。此任務(wù)失敗的原因有很多轮听,包括指定路徑上不存在的文件骗露、沒有讀取權(quán)限的文件或沒有以兼容格式編碼的文件。區(qū)分這些不同的情況允許程序解決一些錯(cuò)誤血巍,并與用戶通信它不能解決的任何錯(cuò)誤萧锉。

Swift中的錯(cuò)誤處理與Cocoa和Objective-C中使用NSError類的錯(cuò)誤處理模式進(jìn)行交互。有關(guān)此類的更多信息述寡,請(qǐng)參見 Handling Cocoa Errors in Swift.

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

在Swift中驹暑,錯(cuò)誤由符合錯(cuò)誤協(xié)議的類型值表示。此空協(xié)議表示可以使用類型進(jìn)行錯(cuò)誤處理辨赐。

Swift枚舉特別適合對(duì)一組相關(guān)錯(cuò)誤條件進(jìn)行建模优俘,相關(guān)值允許傳遞關(guān)于錯(cuò)誤性質(zhì)的附加信息。例如掀序,你可以這樣表示游戲中操作自動(dòng)售貨機(jī)的錯(cuò)誤條件:

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

拋出錯(cuò)誤可以指示發(fā)生了意外事件帆焕,正常的執(zhí)行流程無法繼續(xù)。使用throw語句拋出錯(cuò)誤不恭。例如叶雹,下面的代碼拋出一個(gè)錯(cuò)誤,表示自動(dòng)售貨機(jī)需要額外的5個(gè)硬幣:

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

Handling Errors 處理錯(cuò)誤

當(dāng)拋出錯(cuò)誤時(shí)换吧,周圍的一些代碼必須負(fù)責(zé)處理錯(cuò)誤—例如折晦,通過糾正問題、嘗試另一種方法或通知用戶錯(cuò)誤沾瓦。

在Swift中有四種處理錯(cuò)誤的方法满着。您可以將錯(cuò)誤從函數(shù)傳播到調(diào)用該函數(shù)的代碼,使用do-catch語句處理錯(cuò)誤贯莺,將錯(cuò)誤作為可選值處理风喇,或者斷言錯(cuò)誤不會(huì)發(fā)生。下面一節(jié)將描述每種方法缕探。

當(dāng)一個(gè)函數(shù)拋出一個(gè)錯(cuò)誤時(shí)魂莫,它會(huì)改變程序的流程,所以快速識(shí)別代碼中可能拋出錯(cuò)誤的位置非常重要爹耗。要識(shí)別代碼中的這些位置耙考,請(qǐng)編寫try關(guān)鍵字或try?或者嘗試!變量——在調(diào)用可能引發(fā)錯(cuò)誤的函數(shù)、方法或初始化器的代碼段之前潭兽。下面幾節(jié)將描述這些關(guān)鍵字倦始。

Swift中的錯(cuò)誤處理類似于其他語言中的異常處理,使用try讼溺、catch和throw關(guān)鍵字楣号。與許多語言中的異常處理(包括Swift中的objective - c錯(cuò)誤處理)不同,Swift中的異常處理不涉及撤銷調(diào)用堆棧,這一過程的計(jì)算代價(jià)可能很高炫狱。因此藻懒,throw語句的性能特征可以與return語句的性能特征進(jìn)行比較。

Propagating Errors Using Throwing Functions 使用拋出函數(shù)傳播錯(cuò)誤

要指示函數(shù)视译、方法或初始化器可能引發(fā)錯(cuò)誤嬉荆,可以在函數(shù)的聲明中在其參數(shù)之后寫入throw關(guān)鍵字。用拋出標(biāo)記的函數(shù)稱為拋出函數(shù)酷含。如果函數(shù)指定了返回類型鄙早,則在返回箭頭(->)之前編寫throw關(guān)鍵字。

func canThrowErrors() throws -> String

func cannotThrowErrors() -> String

拋出函數(shù)將內(nèi)部拋出的錯(cuò)誤傳播到調(diào)用它的范圍椅亚。

只有拋出函數(shù)才能傳播錯(cuò)誤限番。在非拋出函數(shù)中拋出的任何錯(cuò)誤都必須在函數(shù)中處理。

在下面的例子中呀舔,VendingMachine類有一個(gè)vend(itemNamed:)方法弥虐,如果請(qǐng)求的項(xiàng)目不可用、缺貨或成本超過當(dāng)前存款金額媚赖,該方法將拋出一個(gè)適當(dāng)?shù)腣endingMachineError:

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語句提前退出方法霜瘪,如果不滿足購買零食的任何要求,則拋出適當(dāng)?shù)腻e(cuò)誤惧磺。因?yàn)閽伋稣Z句會(huì)立即轉(zhuǎn)移程序控制颖对,所以只有在滿足所有這些要求時(shí)才會(huì)出售項(xiàng)目。

因?yàn)関end(itemNamed:)方法傳播它拋出的任何錯(cuò)誤磨隘,所以調(diào)用該方法的任何代碼都必須處理這些錯(cuò)誤——使用do-catch語句缤底,try?,或者試試!或者繼續(xù)傳播。例如琳拭,下面示例中的buyFavoriteSnack(person:vendingMachine:)也是一個(gè)拋出函數(shù)训堆,vend(itemNamed:)方法拋出的任何錯(cuò)誤都會(huì)傳播到調(diào)用buyFavoriteSnack(person:vendingMachine:)函數(shù)的地方。

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ù)查找給定的人最喜歡的零食,并試圖通過調(diào)用vend(itemNamed:)方法為他們購買膘流。因?yàn)関end(itemNamed:)方法可能拋出錯(cuò)誤絮缅,所以調(diào)用它時(shí)在它前面加上try關(guān)鍵字。

拋出初始化器可以像拋出函數(shù)一樣傳播錯(cuò)誤呼股。例如耕魄,下面清單中的PurchasedSnack結(jié)構(gòu)的初始化器調(diào)用一個(gè)拋出函數(shù)作為初始化過程的一部分,并通過將其傳播給調(diào)用者來處理遇到的任何錯(cuò)誤彭谁。

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

Handling Errors Using Do-Catch 使用Do-Catch處理錯(cuò)誤

您可以使用do-catch語句通過運(yùn)行一段代碼來處理錯(cuò)誤吸奴。如果do子句中的代碼拋出了一個(gè)錯(cuò)誤,那么它將與catch子句進(jìn)行匹配,以確定哪個(gè)子句可以處理這個(gè)錯(cuò)誤则奥。

以下是do-catch語句的一般形式:

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

您可以在catch之后編寫一個(gè)模式來指示子句可以處理哪些錯(cuò)誤考润。如果catch子句沒有模式,則子句匹配任何錯(cuò)誤并將錯(cuò)誤綁定到名為error的本地常量读处。有關(guān)模式匹配的更多信息糊治,請(qǐng)參見 Patterns

例如罚舱,下面的代碼匹配VendingMachineError枚舉的所有三種情況井辜。

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表達(dá)式中被調(diào)用管闷,因?yàn)樗鼤?huì)拋出一個(gè)錯(cuò)誤粥脚。如果拋出錯(cuò)誤,執(zhí)行立即轉(zhuǎn)移到catch子句包个,該子句決定是否允許繼續(xù)傳播刷允。如果沒有匹配模式,則由final catch子句捕獲錯(cuò)誤赃蛛,并將其綁定到一個(gè)本地錯(cuò)誤常量恃锉。如果沒有拋出錯(cuò)誤,則執(zhí)行do語句中的其余語句呕臂。

catch子句不必處理do子句中的代碼可能拋出的所有錯(cuò)誤破托。如果catch子句沒有處理錯(cuò)誤,則錯(cuò)誤將傳播到周圍的范圍歧蒋。但是土砂,傳播的錯(cuò)誤必須由周圍的范圍處理。在非拋出函數(shù)中谜洽,必須包含do-catch子句來處理錯(cuò)誤萝映。在拋出函數(shù)中,要么包含do-catch子句阐虚,要么調(diào)用者必須處理錯(cuò)誤序臂。如果錯(cuò)誤在沒有處理的情況下傳播到頂層范圍,您將得到一個(gè)運(yùn)行時(shí)錯(cuò)誤实束。

例如奥秆,上面的例子可以這樣寫,任何不是VendingMachineError的錯(cuò)誤都會(huì)被調(diào)用函數(shù)捕獲:

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

在nourish(with:) 函數(shù)中咸灿,如果vend(itemNamed:)拋出一個(gè)錯(cuò)誤构订,這是VendingMachineError枚舉的一種情況,那么nourish(with:) 通過打印一條消息來處理這個(gè)錯(cuò)誤避矢。否則nourish(with:) 將錯(cuò)誤傳播到它的調(diào)用站點(diǎn)悼瘾。然后由general catch子句捕獲錯(cuò)誤囊榜。

Converting Errors to Optional Values 將錯(cuò)誤轉(zhuǎn)換為可選值

你通過 用 try? 將錯(cuò)誤轉(zhuǎn)換為可選值 來處理錯(cuò)誤。如果在計(jì)算 try? expression評(píng)估表達(dá)式的值為nil, 拋出錯(cuò)誤亥宿。例如卸勺,在下面的代碼中,x和y具有相同的值和行為:

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

let x = try? someThrowingFunction()

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

如果someThrowingFunction()拋出錯(cuò)誤箩绍,則x和y的值為nil孔庭。否則,x和y的值就是函數(shù)返回的值材蛛。注意圆到,x和y是someThrowingFunction()返回的任何類型的可選函數(shù)。這里函數(shù)返回一個(gè)整數(shù)卑吭,所以x和y是可選整數(shù)芽淡。

當(dāng)您希望以相同的方式處理所有錯(cuò)誤時(shí),用 try? 允許您編寫簡潔的錯(cuò)誤處理代碼豆赏。例如挣菲,下面的代碼使用幾種方法來獲取數(shù)據(jù),如果所有方法都失敗掷邦,則返回nil白胀。

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

Disabling Error Propagation 禁用錯(cuò)誤傳播

有時(shí),你知道拋出函數(shù)或方法在運(yùn)行時(shí)實(shí)際上并不會(huì)拋出錯(cuò)誤抚岗。在這些場景或杠,你可以在表達(dá)式之前使用 try! 來禁止錯(cuò)誤傳播并將調(diào)用包裝在斷言中運(yùn)行 ,斷言不會(huì)拋出錯(cuò)誤宣蔚,如果實(shí)際上發(fā)生錯(cuò)誤向抢,你會(huì)得到一個(gè)運(yùn)行時(shí)錯(cuò)誤。

例如胚委,下面的代碼使用一個(gè)loadImage(atPath:)函數(shù)挟鸠,該函數(shù)以給定的路徑加載圖像資源,如果無法加載圖像亩冬,則拋出一個(gè)錯(cuò)誤艘希。在本例中,由于應(yīng)用程序附帶了圖像硅急,因此在運(yùn)行時(shí)不會(huì)拋出錯(cuò)誤枢冤,因此禁用錯(cuò)誤傳播是合適的。

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

Specifying Cleanup Actions 指定清理行動(dòng)

在代碼執(zhí)行離開當(dāng)前代碼塊之前铜秆,使用deferred語句執(zhí)行一組語句。該語句允許您執(zhí)行任何必要的清理工作讶迁,無論執(zhí)行如何離開當(dāng)前代碼塊——不管它是由于拋出錯(cuò)誤還是由于return或break之類的語句而離開的——都應(yīng)該執(zhí)行這些清理工作连茧。例如核蘸,可以使用deferred語句確保關(guān)閉文件描述符并釋放手動(dòng)分配的內(nèi)存。

延遲語句將執(zhí)行延遲到當(dāng)前范圍退出啸驯。該語句由defer關(guān)鍵字和稍后執(zhí)行的語句組成客扎。遞延語句可能不包含將控制權(quán)從語句中轉(zhuǎn)移出去的任何代碼,例如break或return語句罚斗,或者通過拋出錯(cuò)誤徙鱼。延遲操作的執(zhí)行順序與在源代碼中編寫它們的順序相反。也就是說针姿,第一個(gè)deferred語句中的代碼最后執(zhí)行袱吆,第二個(gè)deferred語句中的代碼倒數(shù)執(zhí)行,依此類推距淫。源代碼順序中的最后一個(gè)deferred語句首先執(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è)deferred語句來確保open(:)函數(shù)有一個(gè)對(duì)應(yīng)的調(diào)用來close(:)。

即使不涉及錯(cuò)誤處理代碼榕暇,也可以使用deferred語句蓬衡。

<<返回目錄

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市彤枢,隨后出現(xiàn)的幾起案子狰晚,更是在濱河造成了極大的恐慌,老刑警劉巖缴啡,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件壁晒,死亡現(xiàn)場離奇詭異,居然都是意外死亡盟猖,警方通過查閱死者的電腦和手機(jī)讨衣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來式镐,“玉大人反镇,你說我怎么就攤上這事∧锕” “怎么了歹茶?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長你弦。 經(jīng)常有香客問我惊豺,道長,這世上最難降的妖魔是什么禽作? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任尸昧,我火速辦了婚禮,結(jié)果婚禮上旷偿,老公的妹妹穿的比我還像新娘烹俗。我一直安慰自己爆侣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布幢妄。 她就那樣靜靜地躺著兔仰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蕉鸳。 梳的紋絲不亂的頭發(fā)上乎赴,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音潮尝,去河邊找鬼榕吼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛衍锚,可吹牛的內(nèi)容都是我干的友题。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼戴质,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼度宦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起告匠,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤戈抄,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后后专,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體划鸽,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年戚哎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裸诽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡型凳,死狀恐怖丈冬,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情甘畅,我是刑警寧澤埂蕊,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站疏唾,受9級(jí)特大地震影響蓄氧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜槐脏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一喉童、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧顿天,春花似錦泄朴、人聲如沸重抖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至畔规,卻和暖如春局扶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背叁扫。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國打工三妈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莫绣。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓畴蒲,卻偏偏與公主長得像,于是被迫代替她去往敵國和親对室。 傳聞我的和親對(duì)象是個(gè)殘疾皇子模燥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容