【Swift 3.1】18 - 錯(cuò)誤處理 (Error Handling)
自從蘋果2014年發(fā)布Swift,到現(xiàn)在已經(jīng)兩年多了,而Swift也來(lái)到了3.1版本。去年利用工作之余,共花了兩個(gè)多月的時(shí)間把官方的Swift編程指南看完。現(xiàn)在整理一下筆記,回顧一下以前的知識(shí)。有需要的同學(xué)可以去看官方文檔>>。
錯(cuò)誤處理是在程序中響應(yīng)錯(cuò)誤條件和恢復(fù)錯(cuò)誤條件的過(guò)程。
表示和拋出錯(cuò)誤 (Representing and Throwing Errors)
在Swift中,錯(cuò)誤是用遵循了Error協(xié)議的類型的值來(lái)表示。枚舉非常適合用來(lái)封裝相關(guān)的錯(cuò)誤。
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
是用throw來(lái)拋出錯(cuò)誤:
throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
處理錯(cuò)誤 (Handling Errors)
當(dāng)錯(cuò)誤拋出后,一些相關(guān)的代碼必須處理錯(cuò)誤,例如改正錯(cuò)誤、嘗試另外一種辦法或者告知用戶有錯(cuò)誤。
在Swift中,有四種方法來(lái)處理錯(cuò)誤。把錯(cuò)誤傳遞給調(diào)用這個(gè)方法的代碼;使用do-catch語(yǔ)句處理錯(cuò)誤;把錯(cuò)誤處理為一個(gè)可選類型的值;或者斷言這個(gè)錯(cuò)誤不會(huì)發(fā)生。下面會(huì)演示這四個(gè)方法。
當(dāng)一個(gè)方法拋出了錯(cuò)誤,它會(huì)改變程序的流程,所以及時(shí)發(fā)現(xiàn)錯(cuò)誤的位置非常重要。為了發(fā)現(xiàn)錯(cuò)誤的位置,在調(diào)用方法或初始化器的代碼前使用try、try?或者try!。
使用拋出方法來(lái)傳遞錯(cuò)誤 (Propagating Errors Using Throwing Functions)
為了表示一個(gè)方法或初始化器可以拋出異常,在方法參數(shù)后面加上throw關(guān)鍵字:
func canThrowErrors() throws -> String
func cannotThrowErrors() -> String
注意:只有拋出方法才能傳遞錯(cuò)誤,不能拋出錯(cuò)誤的方法只能在方法內(nèi)處理錯(cuò)誤。
下面是一個(gè)例子:
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語(yǔ)句來(lái)拋出錯(cuò)誤。
因?yàn)?code>vend(itemNamed:)把錯(cuò)誤傳出去了,所以調(diào)用這個(gè)方法的代碼必須處理錯(cuò)誤:使用do-catch語(yǔ)句、try?、try!或者繼續(xù)把錯(cuò)誤傳出去。下面演示的是用繼續(xù)把錯(cuò)誤傳出去的方法來(lái)處理:
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)
}
因?yàn)?code>vend(itemNamed:)能拋出錯(cuò)誤,所以能再前面加上try。
初始化器也能拋出錯(cuò)誤:
struct PurchasedSnack {
let name: String
init(name: String, vendingMachine: VendingMachine) throws {
try vendingMachine.vend(itemNamed: name)
self.name = name
}
}
使用Do-Catch來(lái)處理錯(cuò)誤 (Handling Errors Using Do-Catch)
do-catch語(yǔ)句的通用形式:
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
在catch后面寫一個(gè)樣式來(lái)提示什么樣的錯(cuò)誤能被這個(gè)catch語(yǔ)句處理。如果catch語(yǔ)句沒(méi)有樣式,那么這個(gè)語(yǔ)句會(huì)匹配任何錯(cuò)誤,并且綁定這個(gè)錯(cuò)誤作為一個(gè)本地常量error。
catch語(yǔ)句不必處理每一個(gè)可能的錯(cuò)誤。如果沒(méi)有catch語(yǔ)句處理這個(gè)錯(cuò)誤,那么這個(gè)錯(cuò)誤將會(huì)傳遞給周圍——要么用do-catch語(yǔ)句處理,要么通過(guò)內(nèi)部的拋出方法。例如下面這個(gè)例子除了VendingMachineError的所有枚舉值,但其他錯(cuò)誤只能由周圍的代碼去處理:
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."
把錯(cuò)誤轉(zhuǎn)換為可選值 (Converting Error to Optional Values)
使用try?把錯(cuò)誤轉(zhuǎn)為可選值。在使用try?的語(yǔ)句中,如果有錯(cuò)誤拋出,那么這個(gè)語(yǔ)句的值為nil。
例如下面這個(gè)例子,x和y的值相同:
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
當(dāng)用同一個(gè)方法來(lái)處理所有錯(cuò)誤時(shí),使用try?能是代碼更簡(jiǎn)潔:
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
禁用錯(cuò)誤傳遞 (Disabling Error Propagation)
有時(shí)候我們知道一個(gè)能拋出錯(cuò)誤的方法在運(yùn)行過(guò)程中時(shí)間上不會(huì)拋出錯(cuò)誤。在這種情況下,我們可以在語(yǔ)句前使用try!來(lái)禁用錯(cuò)誤傳遞,并且可以封裝在斷言內(nèi),如果真的有錯(cuò)誤拋出,那么程序報(bào)運(yùn)行時(shí)錯(cuò)誤。
例如:
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
指定清理操作 (Specifying Cleanup Actions)
使用defer語(yǔ)句在代碼執(zhí)行離開當(dāng)前代碼塊之前執(zhí)行一些語(yǔ)句。不管代碼執(zhí)行如何離開當(dāng)前代碼塊,不管是因?yàn)閳?bào)錯(cuò)、return或者break,defer中的語(yǔ)句都能讓我們做一些必要的清理。例如,可以使用defer語(yǔ)句來(lái)保證文件描述符被關(guān)閉和手動(dòng)分配的內(nèi)存被釋放。
defer語(yǔ)句直到當(dāng)前代碼塊退出時(shí)才會(huì)執(zhí)行。不能包括轉(zhuǎn)移控制語(yǔ)句,例如break或者return,或者拋出一個(gè)錯(cuò)誤。延遲操作將按照他們定義的順序的反序執(zhí)行,也就是說(shuō),第一個(gè)defer語(yǔ)句在第二個(gè)defer語(yǔ)句之后執(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è)例子使用defer語(yǔ)句來(lái)保證open(_:)方法有對(duì)應(yīng)的close(_:)方法。
注意:即使沒(méi)有涉及錯(cuò)誤處理代碼也可以使用defer語(yǔ)句。
第十八部分完。下個(gè)部分:【Swift 3.1】19 - 類型轉(zhuǎn)換 (Type Casting)
如果有錯(cuò)誤的地方,歡迎指正!謝謝!