// 錯誤處理
//個人理解: 拋出錯誤用throw關鍵字。
// 在有可能拋出錯誤的 函數(shù)聲明里加入throws關鍵字,稱為throw函數(shù)我碟,可以傳遞該錯誤, 不影響沒有錯誤時候返回正常的返回值翅阵。
// 調(diào)用可以拋出錯誤的函數(shù),使用try關鍵字赚抡。
//“錯誤處理(Error handling)是響應錯誤以及從錯誤中恢復的過程。Swift 提供了在運行時對可恢復錯誤的拋出纠屋、捕獲狱掂、傳遞和操作的一等公民支持「破ぃ”
//“可選類型可用來表示值缺失辫愉,但是當某個操作失敗時,最好能得知失敗的原因族铆,從而可以作出相應的應對岩四。”
//1.表示并拋出錯誤
//“Swift 的枚舉類型尤為適合構建一組相關的錯誤狀態(tài)哥攘,枚舉的關聯(lián)值還可以提供錯誤狀態(tài)的額外信息剖煌。例如,你可以這樣表示在一個游戲中操作自動販賣機時可能會出現(xiàn)的錯誤狀態(tài):”
enum VendingMachineError:Error{
case invalideSelection //選擇無效
case insufficientFunds(coinsNeeded:Int) //金額不足
case outOfStack //缺貨
}
//“拋出一個錯誤可以讓你表明有意外情況發(fā)生逝淹,導致正常的執(zhí)行流程無法繼續(xù)執(zhí)行耕姊。拋出錯誤使用throw關鍵字”
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
//2. 處理錯誤
//“Swift 中有4種處理錯誤的方式。你可以把函數(shù)拋出的錯誤傳遞給調(diào)用此函數(shù)的代碼栅葡、用do-catch語句處理錯誤箩做、將錯誤作為可選類型處理、或者斷言此錯誤根本不會發(fā)生”
//“當一個函數(shù)拋出一個錯誤時妥畏,你的程序流程會發(fā)生改變邦邦,所以重要的是你能迅速識別代碼中會拋出錯誤的地方。為了標識出這些地方醉蚁,在 調(diào)用一個能拋出錯誤的函數(shù)燃辖、方法或者構造器之前,加上try關鍵字网棍, 或者try?或try!這種變體”
//“Swift 中的錯誤處理和其他語言中用try黔龟,catch和throw進行異常處理很像。和其他語言中(包括 Objective-C )的異常處理不同的是,Swift 中的錯誤處理并不涉及解除調(diào)用棧氏身,這是一個計算代價高昂的過程巍棱。就此而言,throw語句的性能特性是可以和return語句相媲美的蛋欣『结悖”
//2.1 用throwing函數(shù)傳遞錯誤
//“為了表示一個函數(shù)、方法或構造器可以拋出錯誤陷虎,在 函數(shù)聲明 的參數(shù)列表之后 加上throws關鍵字到踏。一個標有throws關鍵字的函數(shù)被稱作throwing 函數(shù)。如果這個函數(shù)指明了返回值類型尚猿,throws關鍵詞需要寫在箭頭(->)的前面窝稿。”
//func canThrowError(name:String) throws -> String
//func cannotThrowError(name:String) ->String
//“一個 throwing 函數(shù)可以在其內(nèi)部拋出錯誤凿掂,并將錯誤傳遞到函數(shù) 被調(diào)用時的作用域
//“注意 只有 throwing 函數(shù)可以傳遞錯誤伴榔。任何在某個非 throwing 函數(shù)內(nèi)部拋出的錯誤只能在函數(shù)內(nèi)部處理。
struct Item {
var price:Int
var count:Int
}
class VendingMachine{
var inventory = ["candybar":Item(price:12,count:7),
"chips":Item(price:10,count:4),
"pretzels":Item(price:7,count:11)
]
var coinsDesposited = 0
func dispenseSnacek(snack:String){
print("dispensing\(snack)")
}
func vend(itemNamed name:String)throws {
guard let item = inventory[name] else {
throw VendingMachineError.invalideSelection
}
guard item.count > 0 else {
throw VendingMachineError.outOfStack
}
guard item.price < coinsDesposited else{
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDesposited)
}
coinsDesposited -= item.price
var newItem = item
newItem.count -= 1
inventory[name] = newItem
print("dispending \(name)")
}
}
//“在vend(itemNamed:)方法的實現(xiàn)中使用了guard語句來提前退出方法庄萎,確保在購買某個物品所需的條件中踪少,有任一條件不滿足時,能提前退出方法并拋出相應的錯誤惨恭。由于throw語句會立即退出方法,所以物品只有在所有條件都滿足時才會被售出耙旦⊥严郏”
//“因為vend(itemNamed:)方法會傳遞出它拋出的任何錯誤,在你的代碼中調(diào)用此方法的地方免都,必須要么直接處理這些錯誤——使用do-catch語句锉罐,try?或try!;要么繼續(xù)將這些錯誤傳遞下去绕娘。例如下面例子中脓规,buyFavoriteSnack(_:vendingMachine:)同樣是一個 throwing 函數(shù),任何由vend(itemNamed:)方法拋出的錯誤會一直被傳遞到buyFavoriteSnack(person:vendingMachine:)函數(shù)被調(diào)用的地方险领。
let facoriteSnacks = ["Alice":"chips",
"Bob":"licorice",
"Eve":"pretzels"
]
func buyFavoriteSnack(person:String,vendingMachine:VendingMachine)throws{
let snakeName = facoriteSnacks[person] ?? "candybar"
try vendingMachine.vend(itemNamed: snakeName)
}
//“buyFavoriteSnack(person:vendingMachine:)函數(shù)會查找某人最喜歡的零食侨舆,并通過調(diào)用vend(itemNamed:)方法來嘗試為他們購買。因為vend(itemNamed:)方法能拋出錯誤绢陌,所以在調(diào)用的它時候在它前面加了try關鍵字挨下。”
//2.2 用do-catch來處理錯誤
//“可以使用一個do-catch語句運行一段閉包代碼來處理錯誤脐湾。如果在do子句中的代碼拋出了一個錯誤臭笆,這個錯誤會與catch子句做匹配,從而決定哪條子句能處理它〕钇蹋”
//“在catch后面寫一個匹配模式來表明這個子句能處理什么樣的錯誤鹰霍。如果一條catch子句沒有指定匹配模式,那么這條子句可以匹配任何錯誤茵乱,并且把錯誤綁定到一個名字為error的局部常量”
//“catch子句不必將do子句中的代碼所拋出的每一個可能的錯誤都作處理茂洒。如果所有catch子句都未處理錯誤,錯誤就會傳遞到周圍的作用域似将。然而获黔,錯誤還是必須要被某個周圍的作用域處理的——要么是一個外圍的do-catch錯誤處理語句,要么是一個 throwing 函數(shù)的內(nèi)部”
var vendingMachine = VendingMachine()
vendingMachine.coinsDesposited = 8
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.invalideSelection {
print("invalideSelection")
}catch VendingMachineError.outOfStack {
print("out of stack")
}catch VendingMachineError.insufficientFunds(let coinsNeeded){
print("insufficient funds please insert an additional \(coinsNeeded) coins")
}
// 打印 “Insufficient funds. Please insert an additional 2 coins.”
//“上面的例子中在验,buyFavoriteSnack(person:vendingMachine:)函數(shù)在一個try表達式中調(diào)用玷氏,因為它能拋出錯誤。如果錯誤被拋出腋舌,相應的執(zhí)行會馬上轉移到catch子句中盏触,并判斷這個錯誤是否要被繼續(xù)傳遞下去。如果沒有錯誤拋出块饺,do子句中余下的語句就會被執(zhí)行赞辩。
//2.3 將錯誤轉換成可選值
// “可以使用try?通過將錯誤轉換成一個可選值來處理錯誤。如果在評估try?表達式時一個錯誤被拋出授艰,那么表達式的值就是nil,例如,在下面的代碼中,x和y有著相同的數(shù)值和等價的含義
func someThrowingFunction() throws ->Int{
return Int(2)
}
let x = try?someThrowingFunction()
let y:Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
//“如果someThrowingFunction()拋出一個錯誤辨嗽,x和y的值是nil。否則x和y的值就是該函數(shù)的返回值淮腾。注意糟需,無論someThrowingFunction()的返回值類型是什么類型,x和y都是這個類型的可選類型谷朝。例子中此函數(shù)返回一個整型洲押,所以x和y是可選整型≡不耍”
//“如果你想對所有的錯誤都采用同樣的方式來處理杈帐,用try?就可以讓你寫出簡潔的錯誤處理代碼。例如专钉,下面的代碼用幾種方式來獲取數(shù)據(jù)挑童,如果所有方式都失敗了則返回nil”
func fatchData() -> Data?{
// if let data = try? fatchDataFromDisk(){return data}
// if let data = try? fatchDataFromServer(){return data}
return nil
}
//2.4 禁止錯誤傳遞
// “有時你知道某個throwing函數(shù)實際上在運行時是不會拋出錯誤的,在這種情況下跃须,你可以在表達式前面寫try!來禁用錯誤傳遞炮沐,這會把調(diào)用包裝在一個不會有錯誤拋出的運行時斷言中。如果真的拋出了錯誤回怜,你會得到一個運行時錯誤大年』槐。”
//3. 指定清理操作
//“可以使用defer語句在 即將離開當前代碼塊時執(zhí)行一系列語句 。該語句讓你能執(zhí)行一些必要的清理工作翔试,不管是以何種方式離開當前代碼塊的——無論是由于拋出錯誤而離開轻要,還是由于諸如return或者break的語句”
//“defer語句將代碼的執(zhí)行延遲到當前的作用域退出之前。該語句由defer關鍵字和要被延遲執(zhí)行的語句組成垦缅。延遲執(zhí)行的語句不能包含任何控制轉移語句冲泥,例如break或是return語句,或是拋出一個錯誤壁涎。延遲執(zhí)行的操作會按照它們被指定時的順序的相反順序執(zhí)行——也就是說凡恍,第一條defer語句中的代碼會在第二條defer語句中的代碼被執(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) 會在這里被調(diào)用怔球,即作用域的最后
// }
//}