Error Handling
swift2.0
時代到來,舊項目升級到最新語法恐怕已經讓你焦頭爛額。為此我正打算寫一個關于swift2.0語法講解以及實戰中應用的博客系列。注意:僅僅是語法的改動,我早前已經在github.com中上傳了改動日志,請點擊這里。然而在實際改動項目時,依舊困難重重,甚至不知所措。今天帶來的專題是Error Handling.
0.前言
- 文中將不涉及基礎語法講解,當然我會給出相關參考文檔鏈接。
- 以官方文檔例程為主,當然我會給出詳盡的注釋,以及為什么這么做。
1.如何呈現錯誤以及拋出錯誤
以自動販賣機為例,你可能在購買飲料時遇到以下幾種失敗情景:1.無效的選擇 2.塞入的錢不足以買物品 3.販賣機中貨物售罄。現在來定義一個數據類型來描述錯誤,首先想到的便是枚舉(enum
),為此代碼可以這么寫:
// 例程中只考慮以下三種情況
enum VendingMachineError{
case InvalidSelection //無效選擇
case InSufficientFunds(coinsNeeded:Int) //金額不足
case OutOfStock //貨物售罄
}
這么寫很直觀,并且我們都喜歡用枚舉來定義一些事物的不同情況。那么問題來了,憑什么上面這個枚舉就是用來進行錯誤處理的,其他枚舉則不是? 為此Swift
中定義了ErrorType
協議,而呈現錯誤的值類型都必須遵循這個協議,有趣的是這個協議內容是空的,意味著所有類型無須實現什么就算遵循這個協議了,你只需要在類型后加上:ErrorType
(注:早前ErrorType
并不能夠讓所有類型都遵循,現在則已經全部適用)。改動后的代碼如下:
// 例程中只考慮以下三種情況 切記加上ErrorType 標示它能夠進行錯誤處理
enum VendingMachineError:ErrorType{
case InvalidSelection //無效選擇
case InsufficientFunds(coinsNeeded:Int) //金額不足
case OutOfStock //貨物售罄
}
現在我們可以來談談錯誤發生時的情況了。倘若塞入的錢不足以買貨物,那么販賣機就要給用戶拋出一個錯誤,也就是VendingMachineError.InsufficientFunds(coinsNeeded:5)
,而這只是一個錯誤提示,那么拋這個動詞呢?
顯然swift2.0
中考慮到了,使用throw
聲明拋出錯誤,很形象不是嗎。最后"拋出金額不足錯誤"用代碼語句表述為throw VendingMachineError.InsufficientFunds(coinsNeeded:5)
。
2.處理錯誤
既然有錯誤拋出,自然對應有錯誤處理,那么swift2.0
中是如何實現的呢?仍然以自動販賣機為例,對販賣機做購買請求,同時注意捕獲拋出的錯誤。這句話用代碼描述即為:
do{
//對販賣機試著進行選擇飲料的動作,而這個動作可能會拋出錯誤
}catch{
//捕獲拋出的錯誤 并執行相應措施
}
如此聲明方式,在之后代碼維護時,一眼就能知道這里會拋出錯誤要進行處理。現在我們知道在do
大括號之中,我們需要嘗試一些執行操作,比如調用一些函數,方法或者是構造函數,不過要求只有一個:以上這些東西必須能夠拋出錯誤!!!那么如何聲明一個能夠拋出錯誤的函數,方法呢,請看下文。
3.使用Throwing函數拋出錯誤
在swift
中,早前我們定義一個函數,是這樣的:
func cannotThrowErrors()->String{
// do something
}
從給函數的命名上就可得知該函數是無法拋出錯誤的,那么問題來了,能夠產生并拋出錯誤的函數、方法是如何聲明的呢?其實很簡單,只需要在->
前加上關鍵字throws
即可,至于調用,則只需要使用try
關鍵字即可(try?
以及try!
的用法在之后給出)。
func canThrowErrors()throws ->String{
//這里會將產生的錯誤拋出
}
//調用能夠拋出錯誤的函數
try canThrowErrors()
切記:只有用
throws
關鍵字修飾的函數才能傳遞錯誤。而在正常函數中,只能處理拋出的錯誤。
接著上文的販賣機例程,為販賣機中的購買項聲明一個類,內容包括價格和數量:
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)")
}
}
VendingMachine
販賣機類中默認是有存貨的,存儲到inventory
這個[Item]
數組中,貨物的價格和數量一目了然。接下來為販賣機增添一個“出售”函數,根據用戶輸入的選項進行處理,顯然這個函數是可以拋出錯誤的。因此在VendingMachine
類中最后添加下面這個方法:
//購物 傳入貨物名稱
func vend(itemNamed name: String) throws {
// 通過商品名從清單(字典)中獲取商品,假如我要的東西不存在販賣機里 那么就要拋出錯誤,錯誤如下
// 通過kvo來進行貨物是否在清單內檢查
guard var item = inventory[name] else {
//拋出錯誤:無效的選擇
throw VendingMachineError.InvalidSelection
}
//判斷該商品的數量是否大于0
guard item.count > 0 else {
//拋出錯誤 售罄
throw VendingMachineError.OutOfStock
}
// 塞入的硬幣數目coinsDeposited是否足夠負擔一件項目的價格
guard item.price <= coinsDeposited else {
//錢不夠 則要拋出這個金額不足的錯誤 并捎帶差的金額信息
throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
//OK 錢夠了 買一件 更新販賣機貨品信息 并打印購買信息
coinsDeposited -= item.price
--item.count
inventory[name] = item
dispenseSnack(name)
}
現在販賣機已經能實現簡單的出售行為,是時候讓顧客來購買一波了!
// 1
// 類型:字典 [String:String] -> [人:各自喜歡的食物]
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
// 2
/*:
* brief:這同樣是一個能夠拋出錯誤的函數 看throws關鍵字就一目了然
* para: preson -> 購買點心的人名
* vendingMachine -> 販賣機實例
* return: none
*/
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
//一樣的道理 通過人名字來獲取其喜歡的食物 假如人名不存在 那么默認是Candy Bar 注:??是解包的一種 自行了解
let snackName = favoriteSnacks[person] ?? "Candy Bar"
//OK 調用能夠拋出錯誤的函數 要用try關鍵字
try vend(itemNamed: snackName)
}
-
favoriteSnacks
是一個字典,類型為[String:String]
,鍵對應人名,值對應顧客喜愛的貨物名字。 - 函數傳入參數有兩個,
person
為顧客姓名,通過名字我們可以從favoriteSnacks
中取出他/她喜愛的貨物;vendingMachine
是一個販賣機實例。至于函數主體內容較為簡單,見注釋。
4.使用Do-Catch來進行錯誤處理
正如標題所給出的,我們將使用do-catch
語句來進行錯誤處理,翻譯成白話文也就是做某件能夠拋出錯誤的事情,同時時刻注意捕獲拋出的錯誤進行處理。一般do-catch
聲明形式如下:
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
接下來進行對販賣機的購買操作。
// 1
var vendingMachine = VendingMachine()
// 2
vendingMachine.coinsDeposited = 8
// 3
do {
// 這里使用try關鍵字進行拋出錯誤函數的執行
// 倘若拋出錯誤,會被下面catch體捕獲到
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.")
}
// prints "Insufficient funds. Please insert an additional 2 coins." //還差2塊錢
- 首先實例花了一個販賣機
vendingMachine
- 投入硬幣金額為8元。
- 使用
do-catch
來進行錯誤判斷。可以看到灰常簡單。
5.try try? try!
前文講述了如何定義一個能夠拋出錯誤的函數,只需要在->
前添加throws
關鍵字即可,形如:
func someThrowingFunction() throws -> Int {
// ...
}
當然也說明了使用try
關鍵字來調用執行拋出異常函數,如try someThrowingFunction()
。此處someThrowingFunction
執行后有兩種結果:正常執行返回一個Int
結果值;執行失敗拋出一個錯誤。顯然我們對后者更感興趣,要知道執行失敗意味著結果值就不存在也就是等于nil。通過上文的學習,處理異常函數會這么寫:
let y:Int?
do{
y = try someThrowingFunction()
}catch{
y = nil
}
可以看到處理這種拋出錯誤時,返回值等于nil的處理情況,代碼略長。swift
自然也考慮到了這點,因此加入了try?
。
上文代碼只需一句代碼即可代替let x = try? someThrowingFunction()
。
如此可能還無法打動你的心,那么在舉例來談談try?
在實際應用中的優勢。
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
該函數的職責是從磁盤或服務器讀取數據,可以看到使用try?
之后代碼簡潔易懂,倘若用do-catch
,那滋味真是一個酸爽。
顯然使用try?
處理拋出異常函數時,返回的是一個可選類型,需要進行解包對數據進行操作。確實這么做保證了類型安全,但是假如你已經百分百肯定該拋出異常函數不會拋出錯誤時,我們得到的值必定不為nil
。那么try?
只會加重之后操作的負擔。為此我們可以使用try!
對拋出異常函數處理,前提是你必須保證該函數絕不會拋出錯誤。
let photo = try! loadImage("./Resources/John Appleseed.jpg")
如上,你已經確保了圖片鏈接地址是正確的,所以加載圖片也肯定沒有問題,不會拋出錯誤,那么使用try!
執行這個函數,返回值為照片了,而非一個可選類型。當然馬有失蹄,人有失足,萬一你還是將鏈接地址寫錯了,必定會拋出錯誤,而你又使用了try!
,不好意思,程序崩潰!所以在使用try!
時切記不可馬虎。
總結
初稿,之后進行內容補充以及修改。