【Swift 3.1】18 - 錯誤處理 (Error Handling)
自從蘋果2014年發(fā)布Swift瘩燥,到現(xiàn)在已經(jīng)兩年多了担钮,而Swift也來到了3.1版本檀训。去年利用工作之余妓盲,共花了兩個多月的時間把官方的Swift編程指南看完∨榇猓現(xiàn)在整理一下筆記净赴,回顧一下以前的知識火诸。有需要的同學(xué)可以去看官方文檔>>该窗。
錯誤處理是在程序中響應(yīng)錯誤條件和恢復(fù)錯誤條件的過程。
表示和拋出錯誤 (Representing and Throwing Errors)
在Swift中拌消,錯誤是用遵循了Error
協(xié)議的類型的值來表示挑豌。枚舉非常適合用來封裝相關(guān)的錯誤。
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
是用throw
來拋出錯誤:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
處理錯誤 (Handling Errors)
當(dāng)錯誤拋出后墩崩,一些相關(guān)的代碼必須處理錯誤浮毯,例如改正錯誤、嘗試另外一種辦法或者告知用戶有錯誤泰鸡。
在Swift中,有四種方法來處理錯誤壳鹤。把錯誤傳遞給調(diào)用這個方法的代碼盛龄;使用do-catch
語句處理錯誤;把錯誤處理為一個可選類型的值芳誓;或者斷言這個錯誤不會發(fā)生余舶。下面會演示這四個方法。
當(dāng)一個方法拋出了錯誤锹淌,它會改變程序的流程匿值,所以及時發(fā)現(xiàn)錯誤的位置非常重要。為了發(fā)現(xiàn)錯誤的位置赂摆,在調(diào)用方法或初始化器的代碼前使用try
挟憔、try?
或者try!
钟些。
使用拋出方法來傳遞錯誤 (Propagating Errors Using Throwing Functions)
為了表示一個方法或初始化器可以拋出異常,在方法參數(shù)后面加上throw
關(guān)鍵字:
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
注意:只有拋出方法才能傳遞錯誤绊谭,不能拋出錯誤的方法只能在方法內(nèi)處理錯誤政恍。
下面是一個例子:
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:)
方法的實現(xiàn)中,使用guard
語句來拋出錯誤达传。
因為vend(itemNamed:)
把錯誤傳出去了篙耗,所以調(diào)用這個方法的代碼必須處理錯誤:使用do-catch
語句、try?宪赶、try!或者繼續(xù)把錯誤傳出去宗弯。下面演示的是用繼續(xù)把錯誤傳出去的方法來處理:
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)
}
因為vend(itemNamed:)
能拋出錯誤,所以能再前面加上try
搂妻。
初始化器也能拋出錯誤:
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
使用Do-Catch來處理錯誤 (Handling Errors Using Do-Catch)
do-catch
語句的通用形式:
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
在catch
后面寫一個樣式來提示什么樣的錯誤能被這個catch
語句處理蒙保。如果catch
語句沒有樣式,那么這個語句會匹配任何錯誤叽讳,并且綁定這個錯誤作為一個本地常量error
追他。
catch
語句不必處理每一個可能的錯誤。如果沒有catch
語句處理這個錯誤岛蚤,那么這個錯誤將會傳遞給周圍——要么用do-catch
語句處理邑狸,要么通過內(nèi)部的拋出方法。例如下面這個例子除了VendingMachineError
的所有枚舉值涤妒,但其他錯誤只能由周圍的代碼去處理:
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack(person: "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.")
}
// Prints "Insufficient funds. Please insert an additional 2 coins."
把錯誤轉(zhuǎn)換為可選值 (Converting Error to Optional Values)
使用try?
把錯誤轉(zhuǎn)為可選值单雾。在使用try?
的語句中,如果有錯誤拋出她紫,那么這個語句的值為nil
硅堆。
例如下面這個例子,x和y的值相同:
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
當(dāng)用同一個方法來處理所有錯誤時贿讹,使用try?
能是代碼更簡潔:
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
禁用錯誤傳遞 (Disabling Error Propagation)
有時候我們知道一個能拋出錯誤的方法在運行過程中時間上不會拋出錯誤渐逃。在這種情況下,我們可以在語句前使用try!
來禁用錯誤傳遞民褂,并且可以封裝在斷言內(nèi)茄菊,如果真的有錯誤拋出,那么程序報運行時錯誤赊堪。
例如:
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
指定清理操作 (Specifying Cleanup Actions)
使用defer
語句在代碼執(zhí)行離開當(dāng)前代碼塊之前執(zhí)行一些語句面殖。不管代碼執(zhí)行如何離開當(dāng)前代碼塊,不管是因為報錯哭廉、return
或者break
脊僚,defer
中的語句都能讓我們做一些必要的清理。例如遵绰,可以使用defer
語句來保證文件描述符被關(guān)閉和手動分配的內(nèi)存被釋放辽幌。
defer
語句直到當(dāng)前代碼塊退出時才會執(zhí)行增淹。不能包括轉(zhuǎn)移控制語句,例如break
或者return
舶衬,或者拋出一個錯誤埠通。延遲操作將按照他們定義的順序的反序執(zhí)行,也就是說逛犹,第一個defer
語句在第二個defer
語句之后執(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.
}
}
上面這個例子使用defer
語句來保證open(_:)
方法有對應(yīng)的close(_:)
方法。
注意:即使沒有涉及錯誤處理代碼也可以使用defer
語句虽画。
第十八部分完舞蔽。下個部分:【Swift 3.1】19 - 類型轉(zhuǎn)換 (Type Casting)
如果有錯誤的地方,歡迎指正码撰!謝謝渗柿!