第三章 捕獲錯誤
我們會在這一章中學(xué)到
- 使用返回值來處理錯誤
- 使用 NSError 來處理錯誤
- 使用 Swift 2 的錯誤處理
- 怎么使用 guard 語句
- 怎么使用 defer 關(guān)鍵字
- 何時使用每個錯誤處理模式
使用 guard 語句
var x = 9
if x > 10 {
// Functional code here
} else {
// Do error condition
}
換成 guard 語句就是這樣:
var x = 9
guard x > 10 else {
// 做錯誤處理
return
}
// 這兒放功能性代碼
我們創(chuàng)建一個含有 3 個可選屬性的結(jié)構(gòu)體來解釋 guard:
struct Blog {
var author: String?
var name: String?
var url: NSURL?
}
我們需要確保參數(shù) blog 不是 nil睬塌,并且 name 屬性和 author 屬性也不是 nil:
func blogInfo(blog: Blog?) {
if let blog = blog {
if let author = blog.author, name = blog.name{
print("BLOG:")
print(" Author: \(author)")
print(" name: \(name)")
} else {
print("Author or name is nil")
}
} else {
print("Blog is nil")
}
}
在這兒恤浪,錯誤處理信息被放在了最后议慰,我們很難一眼看出速址。我們可以使用 guard 讓錯誤更易讀和管理。guard 語句設(shè)計的目的是把程序控制轉(zhuǎn)移出當前作用域如果條件不滿足的話逆粹。這允許我們在函數(shù)/方法/構(gòu)造函數(shù)中更早地跟蹤錯誤和執(zhí)行錯誤檢查募疮。并且讓代碼更易讀和理解。我們使用 guard 來重寫之前的例子:
func blogInfo2(blog: Blog?) {
guard let blog = blog else {
print("Blog is nil")
return // 函數(shù)結(jié)束僻弹,不再往下執(zhí)行
}
guard let author = blog.author, name = blog.name else {
print("Author or name is nil")
return
}
print("BLOG:")
print(" Author: \(author)")
print(" name: \(name))
}
第一個 guard 語句用于檢測 blog 參數(shù)是否為 nil阿浓。 第二個 guard 語句用于檢測 author 和 name 屬性是否包含 nil 值。如果它們確實包含 nil 值 那么 guard 語句內(nèi)部的語句就被執(zhí)行奢方。注意每個 guard 語句包含了一個 return 語句搔扁。這是因為 guard 語句必須包含像 return 語句那樣的控制轉(zhuǎn)換語句爸舒。
在 optional 綁定中使用 guard 語句很爽,并且新創(chuàng)建的變量在函數(shù)中的其它地方都是可見的稿蹲,而不僅僅在 optional 綁定語句中可見扭勉。這意味著我們能在 blogInfo2 函數(shù)的所有地方使用 blog、author 和 name 變量苛聘。
錯誤處理
我們將使用 Drink 結(jié)構(gòu)體來說明錯誤處理:
struct Drink {
var volume: Double
var caffeine: Double
var temperature: Double
var drinkSize: DrinkSize
var description: String
mutating func drinking(amount: Double) {
volume -= amount
}
mutating func temperatureChange(change: Double) {
temperature += change
}
}
在方法 drinking 中當 amount > volume 時會拋出錯誤涂炎;在方法 temperatureChange 中當溫度變得太熱或太冷時會拋出錯誤:
使用返回值進行錯誤處理
修改上面的方法,當出錯時返回 false 布爾值:
mutating func drinking(amount: Double) -> Bool {
guard amount <= volume else {
return false
}
volume -= amount
return true
}
測試一下:
var myDrink = Drink(volume: 23.5, caffeine: 280,
temperature: 38.2, drinkSize: DrinkSize.Can24,
description: "Drink Structure")
if myDrink.drinking(50.0) {
print("Had a drink")
} else {
print("Error")
}
結(jié)果打印 "Error"设哗。
我們來檢測 drink 的溫度是否合適:
enum DrinkTemperature {
case TooHot
case TooCold
case JustRight
}
如果溫度在 [35, 45]
這個區(qū)間之間唱捣,那么溫度是 JustRight, 剛剛好。如果不在這個區(qū)間之內(nèi)网梢,溫度就不合適:
mutating func temperatureChange(change: Double) -> DrinkTemperature {
temperature += change
guard temperature >= 35 else {
return .TooCold
}
guard temperature <= 45 else {
return .TooHot
}
return .JustRight
}
我們來測試一下:
var results = myDrink.temperatureChange(-5)
switch results {
case .TooHot:
print("Drink too hot")
case .TooCold:
print("Drink too cold")
case .JustRight:
print("Drink just right")
}
如果我們想在錯誤中包含更多信息震缭,那么可以在枚舉中使用關(guān)聯(lián)值。
使用 NSError 進行錯誤處理
NSError 類把錯誤信息封裝到單個對象中战虏。這個對象包含了錯誤域拣宰、特定域的錯誤碼還有含有關(guān)于錯誤的特定信息的用戶信息字典。當我們在 Swift 中使用 NSError 時我們給方法的參數(shù)列表添加了一個 NSErrorPointer 類型的 NSError inout 參數(shù)烦感。
我們首先來定義錯誤域和作為常量的錯誤碼:
struct ErrorConstants {
static let ERROR_DOMAIN = "com.masterswift.nserrorexample"
static let ERROR_INSUFFICENT_VOLUME = 200
static let ERROR_TOO_HOT = 201
static let ERROR_TOO_COLD = 202
}
使用 NSError inout 參數(shù)修改 drinking() 方法巡社。
mutating func drinking(amount: Double, error: NSErrorPointer) -> Bool {
guard amount <= volume else {
error.memory = NSError(
domain: ErrorConstants.ERROR_DOMAIN,
code: ErrorConstants.ERROR_INSUFFICENT_VOLUME,
userInfo: ["Error reason": "Not enough volume for drink"]
)
}
volume -= amount
return true
}
}
注意
NSErrorPointer 實際上是 AutoreleasingUnsafeMutablePointer 結(jié)構(gòu)體的類型別名(typealias)。使用 NSErrorPointer 結(jié)構(gòu)體等價于 Objective-C 中的 NSError**
, 它作為用在方法中的 inout 表達式手趣。
當這個函數(shù)返回 false 時晌该,讓調(diào)用這個方法的代碼知道錯誤出現(xiàn)了,以至于它能檢查 NSError 對象來獲取關(guān)于錯誤的詳細信息绿渣。我們可以像這樣用這個方法:
var myDrink = Drink(volume: 23.5, caffeine:280, temperature: 38.2, drinkSize: DrinkSize.Can24, description: "Drink Struct")
var myError: NSError?
if myDrink.drinking(50, error: &myError) {
print("Had a drink")
} else {
print("Error: \(myError?.code)")
}
在這個例子中朝群,我們創(chuàng)建了一個 Drink 結(jié)構(gòu)體和 NSError 類。我們之后調(diào)用了 Drink 實例的 drinking() 方法怯晕,給它傳遞了我們想喝的 amount 和對我們創(chuàng)建的 NSError 實例的引用潜圃。如果 drinking() 方法返回真,則我們成功地喝了飲料舟茶,并且 myError 實例應(yīng)該為 nil谭期。如果 drinking() 方法返回 false,那么就喝不著飲料了吧凉,我們會打印出帶有錯誤碼的錯誤信息隧出。
我們再來看一下 temperatureChange 方法:
mutating func temperatureChange(change: Double, error: NSErrorPointer) -> Bool {
temperature += change
guard temperature >= 35 else {
error.memory = NSError(
domain: ErrorConstants.ERROR_DOMAIN,
code: ErrorConstants.ERROR_TOO_COLD,
userInfo: ["Error reason": "Drink too cold"]
)
return false
}
guard temperature <= 45 else {
error.memory = NSError(
domain: ErrorConstants.ERROR_DOMAIN,
code: ErrorConstants.ERROR_TOO_HOT,
userInfo: ["Error reason": "Drink too warm"]
)
return false
}
return true
}
使用 Swift 2 進行錯誤處理
表示錯誤
在 Swift 中,錯誤由遵守 ErrorType 協(xié)議的類型的值來表示阀捅。 Swift 中的枚舉很適合給這些錯誤條件模型化胀瞪,因為通常我們要表示的錯誤條件是有限的。我們還可以使用關(guān)聯(lián)值來為我們的錯誤添加額外的信息。
我們來使用枚舉表示錯誤:
enum MyError: ErrorType {
case Minor
case Bad
case Terrible
}
我們定義了一個遵守 ErrorType 協(xié)議的 MyError 枚舉凄诞。我們還可以為錯誤條件添加關(guān)聯(lián)值:
enum MyError: ErrorType {
case Minor
case Bad
case Terrible (description: String)
}
我們來看怎樣為 Drink 類型表示錯誤條件:
enum DrinkErrors: ErrorType {
case insufficentVolume
case tooHot
case tooCold
}
我們可以使用關(guān)聯(lián)值重寫上面的錯誤條件:
enum DrinkErrors: ErrorType {
case insufficentVolume
case tempOutOfRange (Description: String)
}
看起來相當不錯但是不建議把它定義成這樣圆雁。當錯誤發(fā)生的時候,我們想確保我們可以捕獲特定的錯誤并指導(dǎo)如何應(yīng)對帆谍。在這里我們可以在錯誤被拋出時捕獲 tempOutOfRange 錯誤但是隨后我們需要一個額外的查詢以找出溫度是太高還是太低伪朽。這種額外的查詢不是很理想并且會增加不必要的復(fù)雜性。
關(guān)聯(lián)值應(yīng)該用于為錯誤條件添加額外的信息而非用于指定所出現(xiàn)的錯誤類型汛蝙。例如我們可以在關(guān)聯(lián)值中返回實際的溫度:
enum DrinkErrors: ErrorType {
case insufficentVolume
case tooHot (temp: Double)
case tooCold (temp: Double)
}
我們已經(jīng)看到如何定義錯誤烈涮,現(xiàn)在來看看怎么在錯誤發(fā)生時拋出錯誤。
拋出錯誤
/**
This method will take a drink from our drink if we have enough liquid left in our drink.
- Parameter amount: The amount to drink
- Throws: DrinkError.insufficentVolume if there is not enough volume left
*/
mutating func drinking(amount: Double) throws {
guard amount < volume else {
throw DrinkErrors.insufficentVolume
}
volume -= amount
}
如果函數(shù)或方法擁有返回類型窖剑,那么 throws 關(guān)鍵字會出現(xiàn)在參數(shù)列表之后坚洽,返回類型之前:
func myFunc(para: String) throws -> String
當錯誤被拋出后,控制會回到調(diào)用該函數(shù)或方法的代碼中西土。我們來看 temperatureChange() 方法拋出的錯誤:
/**
This method will change the temperature of the drink.
- Parameter change: The amount to change, can be negative or positive
- Throws:
- DrinkError.tooHot if the drink is too hot
- DrinkError.tooCold if the drink is too cold
*/
mutating func temperatureChange(change: Double) throws {
temperature += change
guard temperature > 35 else {
throw DrinkErrors.tooCold(temp: temperature)
}
guard temperature < 45 else {
throw DrinkErrors.tooHot(temp: temperature)
} }
捕獲錯誤
當錯誤從函數(shù)中拋出時讶舰,我們需要在代碼中捕獲它,這是使用 do-catch 塊兒來完成的:
do {
try [Some function that throws an error]
} catch [pattern] {
[Code if function threw error]
}
當我們調(diào)用要拋出錯誤的函數(shù)或方法時翠储,我們必須在調(diào)用前面前置一個 try 關(guān)鍵字绘雁。catch 關(guān)鍵字后面跟著要與錯誤相匹配的模式。如果錯誤與模式匹配援所,則 catch 塊兒中的代碼被執(zhí)行。
我們來看看 drinking() 方法里是怎么捕獲錯誤的:
do {
try myDrink.drinking(50.0)
} catch DrinkErrors.insufficentVolume {
print("Error taking drink")
}
catch 語句嘗試匹配 DrinkErrors.insufficentVolume 錯誤欣除,因為那個錯誤是通過 drinking() 方法拋出的住拭。
我們不一定非要在 catch 語句后面包含一個模式。如果 catch 語句后面沒有跟著模式或者跟著的是一個下劃線历帚,那么那個 catch 語句會匹配所有的錯誤條件滔岳。例如,下面兩個 catch 會捕獲所有的錯誤:
do {
// our statements
} catch {
// our error conditions
}
do {
// our statements
} catch _ {
// our error conditions
}
如果我們我們不確定錯誤是從函數(shù)還是從方法中拋出的挽牢,那么最好包含一個能捕獲所有錯誤條件的 catch 語句以避免在運行時擁有一個未捕獲的錯誤谱煤。如果我們在運行時擁有一個未捕獲的錯誤,那么應(yīng)用就會奔潰禽拔。
如果我們想捕獲錯誤刘离,我們可以使用 let 關(guān)鍵字:
do {
// our statements
} catch let error {
print("Error: \(error)")
}
我們來看怎么從 temperatureChange() 方法中捕獲 DrinkErrors.tooHot 和 DrinkErrors.tooCold 錯誤:
do {
try myDrink.temperatureChange(20.0)
} catch DrinkErrors.tooHot(let temp) {
print("Drink too hot: \(temp) degrees ")
catch DrinkErrors.tooCold(let temp) {
print("Drink too cold: \(temp) degrees")
}
}
我們經(jīng)常會在 Java/C#
中看到很多空的 catch 語句, 在 Swift 中是這樣的:
do {
try myDrink.temperatureChange(20.0)
} catch {}
這看上去有點笨睹栖,不過 Swift 開發(fā)者們有 try?
關(guān)鍵字硫惕。try?
關(guān)鍵字嘗試執(zhí)行一個可能會拋出錯誤的操作。如果那個操作成功了野来,如果有結(jié)果的話恼除,結(jié)果會以可選值的形式返回,如果操作失敗并拋出一個錯誤曼氛,那么該操作返回一個 nil 并丟棄結(jié)果豁辉。我們可以像這樣來使用 try?
關(guān)鍵字:
try? myDrink.temperatureChange(20.0)
如果函數(shù)或方法擁有返回類型令野,我們可以使用可選綁定來捕獲所返回的值:
if let value = try? myMethod(42) {
print("Value returned \(value)")
}
我們可以把錯誤傳送出去而不是立即捕獲它們。我們只需把 throws 關(guān)鍵字添加到函數(shù)定義中去就好了徽级。例如彩掐,在下面的例子中,我們把錯誤傳送給調(diào)用該函數(shù)的代碼而不是在函數(shù)內(nèi)部處理錯誤:
func myFunc throws {
try myDrink.temperatureChange(2.0)
}
我們需要執(zhí)行一些清理動作灰追,不管我們有沒有錯誤堵幽,我們可以使用 defer 語句。我們正好在代碼執(zhí)行離開當前作用域之前使用 defer 語句來執(zhí)行一個代碼塊弹澎。下面的例子展示了怎么使用 defer 語句:
func deferFunction() {
print("Function started")
var str: String?
defer {
print("In defer block")
if let s = str {
print("str is \(str)")
}
}
str = "Jon"
print("Function finished")
}
當我們調(diào)用這個函數(shù)時朴下,首先打印 Function started,接著代碼的執(zhí)行會跳過 defer 塊并打印 Function finished苦蒿。最后殴胧,正好在我們離開函數(shù)作用域之前,defer 代碼塊被執(zhí)行了佩迟,并打印出我們看到的信息:
Function started
Function finished
In defer block
str is Jon
defer 代碼塊總是在執(zhí)行離開當前作用域之前被調(diào)用团滥,即使有錯誤拋出時。在離開函數(shù)之前執(zhí)行某些清理函數(shù)時报强,defer 代碼塊會很有用灸姊。
當我們想確保我們執(zhí)行完了所有必要的清理工作時 defer 語句會很有用,即使有錯誤拋出時秉溉。例如力惯,如果我們成功地打開了一個文件去寫入,我們總是會確保我們關(guān)閉了那個文件召嘶,即使在我們執(zhí)行寫操作的時候有錯誤拋出父晶。我們可以在 defer 代碼塊中加入一個文件關(guān)閉功能以確保在離開當前作用域之前,文件總是被關(guān)閉了弄跌。
什么時候使用錯誤處理
大西瓜
總結(jié)
大西瓜