錯(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語句蓬衡。