錯誤處理(Error handling)是響應錯誤以及從錯誤中恢復的過程蝉稳。Swift 提供了在運行時對可恢復錯誤的拋出宾添、捕獲增热、傳遞和操作的一流支持障贸。
表示并拋出錯誤(Representing and Throwing Errors)
在 Swift 中,錯誤用符合ErrorType協(xié)議的類型的值來表示确垫。這個空協(xié)議表明該類型可以用于錯誤處理弓颈。
Swift 的枚舉類型尤為適合構建一組相關的錯誤狀態(tài)帽芽,枚舉的關聯值還可以提供錯誤狀態(tài)的額外信息。
拋出一個錯誤表明有意外情況發(fā)生翔冀,導致正常的執(zhí)行流程無法繼續(xù)執(zhí)行导街。拋出錯誤使用throw關鍵字。
處理錯誤(Handling Errors)
某個錯誤被拋出時纤子,附近的某部分代碼必須負責處理這個錯誤搬瑰,例如糾正這個問題、嘗試另外一種方式控硼、或是向用戶報告錯誤泽论。
Swift 中有4種處理錯誤的方式。把函數拋出的錯誤傳遞給調用此函數的代碼象颖、用do-catch語句處理錯誤佩厚、將錯誤作為可選類型處理、或者斷言此錯誤根本不會發(fā)生说订。
當一個函數拋出一個錯誤時抄瓦,程序流程會發(fā)生改變,所以重要的是你能迅速識別代碼中會拋出錯誤的地方陶冷。為了標識出這些地方钙姊,在調用一個能拋出錯誤的函數、方法或者構造器之前埂伦,加上try關鍵字煞额,或者try?或try!這種變體。
用 throwing 函數傳遞錯誤(Propagating Errors Using Throwing Functions)
為了表示一個函數沾谜、方法或構造器可以拋出錯誤膊毁,在函數聲明的參數列表之后加上throws關鍵字。一個標有throws關鍵字的函數被稱作throwing 函數基跑。如果這個函數指明了返回值類型婚温,throws關鍵詞需要寫在箭頭(->)的前面。
只有 throwing 函數可以傳遞錯誤媳否。任何在某個非 throwing 函數內部拋出的錯誤只能在函數內部處理栅螟。
用 Do-Catch 處理錯誤(Handling Errors Using Do-Catch)
- 可以使用一個do-catch語句運行一段閉包代碼來處理錯誤。如果在do子句中的代碼拋出了一個錯誤篱竭,這個錯誤會與catch子句做匹配力图,從而決定哪條子句能處理它。
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
在catch后面寫一個匹配模式來表明這個子句能處理什么樣的錯誤掺逼。如果一條catch子句沒有指定匹配模式吃媒,那么這條子句可以匹配任何錯誤,并且把錯誤綁定到一個名字為error的局部常量。
catch子句不必將do子句中的代碼所拋出的每一個可能的錯誤都作處理晓折。如果所有catch子句都未處理錯誤惑朦,錯誤就會傳遞到周圍的作用域。然而漓概,錯誤還是必須要被某個周圍的作用域處理的——要么是一個外圍的do-catch錯誤處理語句,要么是一個 throwing 函數的內部病梢。
將錯誤轉換成可選值(Converting Errors to Optional Values)
可以使用try?通過將錯誤轉換成一個可選值來處理錯誤胃珍。如果在評估try?
表達式時一個錯誤被拋出,那么表達式的值就是nil蜓陌。如果想對所有的錯誤都采用同樣的方式來處理觅彰,用try?就可以寫出簡潔的錯誤處理代碼。
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
禁用錯誤傳遞(Disabling Error Propagation)
- 有時你知道某個 throwing 函數實際上在運行時是不會拋出錯誤的钮热,在這種情況下填抬,你可以在表達式前面寫try!來禁用錯誤傳遞,這會把調用包裝在一個不會有錯誤拋出的運行時斷言中隧期。如果真的拋出了錯誤飒责,會得到一個運行時錯誤。程序崩潰仆潮。
用try? 代替 try!宏蛉,值可以不用,多寫一行if let也沒什么性置,要防止崩潰
enum VendingMachineError: ErrorType {
case InvalidSelection //選擇無效
case InsufficientFunds(coinsNeeded: Int) //金額不足
case OutOfStock //缺貨
}
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 dispenseSnack(snack: String) {
print("Dispensing \(snack)")
}
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
dispenseSnack(name)
}
}
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)
}
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack("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.")
}
// 打印 “Insufficient funds. Please insert an additional 2 coins.”
指定清理操作(Specifying Cleanup Actions)
可以使用defer語句在即將離開當前代碼塊時執(zhí)行一系列語句拾并。該語句讓你能執(zhí)行一些必要的清理工作,不管是以何種方式離開當前代碼塊的——無論是由于拋出錯誤而離開鹏浅,還是由于諸如return或者break的語句嗅义。
例如,可以用defer語句來確保文件描述符得以關閉隐砸,以及手動分配的內存得以釋放之碗。
defer語句將代碼的執(zhí)行延遲到當前的作用域退出之前。該語句由defer關鍵字和要被延遲執(zhí)行的語句組成凰萨。
延遲執(zhí)行的語句不能包含任何控制轉移語句继控,例如break或是return語句,或是拋出一個錯誤胖眷。
延遲執(zhí)行的操作會按照它們被指定時的順序的相反順序執(zhí)行——也就是說武通,第一條defer語句中的代碼會在第二條defer語句中的代碼被執(zhí)行之后才執(zhí)行,以此類推珊搀。
即使沒有涉及到錯誤處理冶忱,你也可以使用defer語句。
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 處理文件境析。
}
// close(file) 會在這里被調用囚枪,即作用域的最后派诬。
}
}
// 使用一條defer語句來確保open(_:)函數有一個相應的對close(_:)函數的調用。
- enum和ErroyType配對使用链沼,推薦默赂。拋出異常,指明錯誤原因括勺,在網絡缆八,文件,數據庫等涉及資源相關概念的場景適合使用
- defer關鍵字疾捍,強烈推薦使用奈辰。文件打開,馬上寫上關閉乱豆,成對出現奖恰。這是一種好的編程習慣。
- guard和throw配對使用宛裕,推薦瑟啃。先檢查條件,再往下執(zhí)行具體任務续滋。
- do - try - catch結構和其他的try-catch-finally結構是不一樣的
- try翰守?是一種簡化寫法,不推薦用疲酌。直接用可選類型的普通函數就好了蜡峰,函數定義時就不要加throw關鍵字了。
- try朗恳!強烈不推薦使用湿颅,用try?代替
- Swift整體的錯誤處理的思路還是值得肯定的粥诫。首先油航,錯誤處理拋出異常會打斷程序的正常執(zhí)行順序,所以并不推薦經常使用怀浆,所以要加throw谊囚、try等關鍵字,增加使用麻煩度执赡。其次镰踏,在真正需要使用拋出異常的地方,位置信息更加精確沙合。最后奠伪,將枚舉和協(xié)議的概念引入,讓錯誤信息的表達更加方便。
- Object-C是使用NSError來處理錯誤的绊率,沒有異常拋出過程谨敛,使用時要傳地址&,這是C語言常見的錯誤處理過程滤否。這樣的好處是程序的執(zhí)行流程不會被打斷脸狸,錯誤信息可以存儲,信息是否使用顽聂,由調用者決定肥惭。
- 兩種錯誤處理方式各有優(yōu)缺點,在Swift中紊搪,還是推薦Swift特有的錯誤處理方式,還算是比較好用好理解的全景。
- 一般情況下耀石,可選類型就足夠表達意思了;在需要處理錯誤信息的場合爸黄,才考慮引入錯誤處理機制滞伟。比如文件讀寫,網絡炕贵,數據庫等等梆奈。