Error Handling
swift2.0
時代到來,舊項目升級到最新語法恐怕已經(jīng)讓你焦頭爛額怒坯。為此我正打算寫一個關(guān)于swift2.0語法講解以及實戰(zhàn)中應(yīng)用的博客系列。注意:僅僅是語法的改動,我早前已經(jīng)在github.com中上傳了改動日志获列,請點擊這里逗爹。然而在實際改動項目時亡嫌,依舊困難重重,甚至不知所措掘而。今天帶來的專題是Error Handling.
0.前言
- 文中將不涉及基礎(chǔ)語法講解挟冠,當(dāng)然我會給出相關(guān)參考文檔鏈接。
- 以官方文檔例程為主袍睡,當(dāng)然我會給出詳盡的注釋知染,以及為什么這么做。
1.如何呈現(xiàn)錯誤以及拋出錯誤
以自動販賣機為例斑胜,你可能在購買飲料時遇到以下幾種失敗情景:1.無效的選擇 2.塞入的錢不足以買物品 3.販賣機中貨物售罄】氐現(xiàn)在來定義一個數(shù)據(jù)類型來描述錯誤,首先想到的便是枚舉(enum
)止潘,為此代碼可以這么寫:
// 例程中只考慮以下三種情況
enum VendingMachineError{
case InvalidSelection //無效選擇
case InSufficientFunds(coinsNeeded:Int) //金額不足
case OutOfStock //貨物售罄
}
這么寫很直觀掺炭,并且我們都喜歡用枚舉來定義一些事物的不同情況。那么問題來了凭戴,憑什么上面這個枚舉就是用來進(jìn)行錯誤處理的涧狮,其他枚舉則不是? 為此Swift
中定義了ErrorType
協(xié)議,而呈現(xiàn)錯誤的值類型都必須遵循這個協(xié)議者冤,有趣的是這個協(xié)議內(nèi)容是空的吧享,意味著所有類型無須實現(xiàn)什么就算遵循這個協(xié)議了,你只需要在類型后加上:ErrorType
(注:早前ErrorType
并不能夠讓所有類型都遵循譬嚣,現(xiàn)在則已經(jīng)全部適用)钢颂。改動后的代碼如下:
// 例程中只考慮以下三種情況 切記加上ErrorType 標(biāo)示它能夠進(jìn)行錯誤處理
enum VendingMachineError:ErrorType{
case InvalidSelection //無效選擇
case InsufficientFunds(coinsNeeded:Int) //金額不足
case OutOfStock //貨物售罄
}
現(xiàn)在我們可以來談?wù)勫e誤發(fā)生時的情況了。倘若塞入的錢不足以買貨物拜银,那么販賣機就要給用戶拋出一個錯誤殊鞭,也就是VendingMachineError.InsufficientFunds(coinsNeeded:5)
,而這只是一個錯誤提示尼桶,那么拋這個動詞呢操灿?
顯然swift2.0
中考慮到了,使用throw
聲明拋出錯誤泵督,很形象不是嗎趾盐。最后"拋出金額不足錯誤"用代碼語句表述為throw VendingMachineError.InsufficientFunds(coinsNeeded:5)
。
2.處理錯誤
既然有錯誤拋出小腊,自然對應(yīng)有錯誤處理救鲤,那么swift2.0
中是如何實現(xiàn)的呢?仍然以自動販賣機為例秩冈,對販賣機做購買請求本缠,同時注意捕獲拋出的錯誤。這句話用代碼描述即為:
do{
//對販賣機試著進(jìn)行選擇飲料的動作入问,而這個動作可能會拋出錯誤
}catch{
//捕獲拋出的錯誤 并執(zhí)行相應(yīng)措施
}
如此聲明方式丹锹,在之后代碼維護(hù)時,一眼就能知道這里會拋出錯誤要進(jìn)行處理》沂В現(xiàn)在我們知道在do
大括號之中楣黍,我們需要嘗試一些執(zhí)行操作,比如調(diào)用一些函數(shù)棱烂,方法或者是構(gòu)造函數(shù)租漂,不過要求只有一個:以上這些東西必須能夠拋出錯誤!9柑洹窜锯!那么如何聲明一個能夠拋出錯誤的函數(shù),方法呢芭析,請看下文锚扎。
3.使用Throwing函數(shù)拋出錯誤
在swift
中,早前我們定義一個函數(shù)馁启,是這樣的:
func cannotThrowErrors()->String{
// do something
}
從給函數(shù)的命名上就可得知該函數(shù)是無法拋出錯誤的驾孔,那么問題來了芍秆,能夠產(chǎn)生并拋出錯誤的函數(shù)、方法是如何聲明的呢翠勉?其實很簡單妖啥,只需要在->
前加上關(guān)鍵字throws
即可,至于調(diào)用对碌,則只需要使用try
關(guān)鍵字即可(try?
以及try!
的用法在之后給出)荆虱。
func canThrowErrors()throws ->String{
//這里會將產(chǎn)生的錯誤拋出
}
//調(diào)用能夠拋出錯誤的函數(shù)
try canThrowErrors()
切記:只有用
throws
關(guān)鍵字修飾的函數(shù)才能傳遞錯誤。而在正常函數(shù)中朽们,只能處理拋出的錯誤怀读。
接著上文的販賣機例程,為販賣機中的購買項聲明一個類骑脱,內(nèi)容包括價格和數(shù)量:
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)
]
// 記錄用戶塞入的硬幣數(shù)目
var coinsDeposited = 0
func dispenseSnack(snack:String){
print("Dispensing \(snack)")
}
}
VendingMachine
販賣機類中默認(rèn)是有存貨的,存儲到inventory
這個[Item]
數(shù)組中叁丧,貨物的價格和數(shù)量一目了然啤誊。接下來為販賣機增添一個“出售”函數(shù),根據(jù)用戶輸入的選項進(jìn)行處理拥娄,顯然這個函數(shù)是可以拋出錯誤的蚊锹。因此在VendingMachine
類中最后添加下面這個方法:
//購物 傳入貨物名稱
func vend(itemNamed name: String) throws {
// 通過商品名從清單(字典)中獲取商品,假如我要的東西不存在販賣機里 那么就要拋出錯誤条舔,錯誤如下
// 通過kvo來進(jìn)行貨物是否在清單內(nèi)檢查
guard var item = inventory[name] else {
//拋出錯誤:無效的選擇
throw VendingMachineError.InvalidSelection
}
//判斷該商品的數(shù)量是否大于0
guard item.count > 0 else {
//拋出錯誤 售罄
throw VendingMachineError.OutOfStock
}
// 塞入的硬幣數(shù)目coinsDeposited是否足夠負(fù)擔(dān)一件項目的價格
guard item.price <= coinsDeposited else {
//錢不夠 則要拋出這個金額不足的錯誤 并捎帶差的金額信息
throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
//OK 錢夠了 買一件 更新販賣機貨品信息 并打印購買信息
coinsDeposited -= item.price
--item.count
inventory[name] = item
dispenseSnack(name)
}
現(xiàn)在販賣機已經(jīng)能實現(xiàn)簡單的出售行為枫耳,是時候讓顧客來購買一波了!
// 1
// 類型:字典 [String:String] -> [人:各自喜歡的食物]
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
// 2
/*:
* brief:這同樣是一個能夠拋出錯誤的函數(shù) 看throws關(guān)鍵字就一目了然
* para: preson -> 購買點心的人名
* vendingMachine -> 販賣機實例
* return: none
*/
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
//一樣的道理 通過人名字來獲取其喜歡的食物 假如人名不存在 那么默認(rèn)是Candy Bar 注:??是解包的一種 自行了解
let snackName = favoriteSnacks[person] ?? "Candy Bar"
//OK 調(diào)用能夠拋出錯誤的函數(shù) 要用try關(guān)鍵字
try vend(itemNamed: snackName)
}
-
favoriteSnacks
是一個字典孟抗,類型為[String:String]
,鍵對應(yīng)人名钻心,值對應(yīng)顧客喜愛的貨物名字凄硼。 - 函數(shù)傳入?yún)?shù)有兩個,
person
為顧客姓名捷沸,通過名字我們可以從favoriteSnacks
中取出他/她喜愛的貨物摊沉;vendingMachine
是一個販賣機實例。至于函數(shù)主體內(nèi)容較為簡單痒给,見注釋说墨。
4.使用Do-Catch來進(jìn)行錯誤處理
正如標(biāo)題所給出的,我們將使用do-catch
語句來進(jìn)行錯誤處理苍柏,翻譯成白話文也就是做某件能夠拋出錯誤的事情尼斧,同時時刻注意捕獲拋出的錯誤進(jìn)行處理。一般do-catch
聲明形式如下:
do {
try expression
statements
} catch pattern 1 {
statements
} catch pattern 2 where condition {
statements
}
接下來進(jìn)行對販賣機的購買操作试吁。
// 1
var vendingMachine = VendingMachine()
// 2
vendingMachine.coinsDeposited = 8
// 3
do {
// 這里使用try關(guān)鍵字進(jìn)行拋出錯誤函數(shù)的執(zhí)行
// 倘若拋出錯誤棺棵,會被下面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
來進(jìn)行錯誤判斷≈蛐簦可以看到灰常簡單母怜。
5.try try? try!
前文講述了如何定義一個能夠拋出錯誤的函數(shù),只需要在->
前添加throws
關(guān)鍵字即可缚柏,形如:
func someThrowingFunction() throws -> Int {
// ...
}
當(dāng)然也說明了使用try
關(guān)鍵字來調(diào)用執(zhí)行拋出異常函數(shù)苹熏,如try someThrowingFunction()
。此處someThrowingFunction
執(zhí)行后有兩種結(jié)果:正常執(zhí)行返回一個Int
結(jié)果值币喧;執(zhí)行失敗拋出一個錯誤柜裸。顯然我們對后者更感興趣,要知道執(zhí)行失敗意味著結(jié)果值就不存在也就是等于nil粱锐。通過上文的學(xué)習(xí)疙挺,處理異常函數(shù)會這么寫:
let y:Int?
do{
y = try someThrowingFunction()
}catch{
y = nil
}
可以看到處理這種拋出錯誤時,返回值等于nil的處理情況怜浅,代碼略長铐然。swift
自然也考慮到了這點,因此加入了try?
恶座。
上文代碼只需一句代碼即可代替let x = try? someThrowingFunction()
搀暑。
如此可能還無法打動你的心,那么在舉例來談?wù)?code>try?在實際應(yīng)用中的優(yōu)勢跨琳。
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
該函數(shù)的職責(zé)是從磁盤或服務(wù)器讀取數(shù)據(jù)自点,可以看到使用try?
之后代碼簡潔易懂,倘若用do-catch
脉让,那滋味真是一個酸爽桂敛。
顯然使用try?
處理拋出異常函數(shù)時,返回的是一個可選類型溅潜,需要進(jìn)行解包對數(shù)據(jù)進(jìn)行操作术唬。確實這么做保證了類型安全,但是假如你已經(jīng)百分百肯定該拋出異常函數(shù)不會拋出錯誤時滚澜,我們得到的值必定不為nil
粗仓。那么try?
只會加重之后操作的負(fù)擔(dān)。為此我們可以使用try!
對拋出異常函數(shù)處理设捐,前提是你必須保證該函數(shù)絕不會拋出錯誤借浊。
let photo = try! loadImage("./Resources/John Appleseed.jpg")
如上,你已經(jīng)確保了圖片鏈接地址是正確的萝招,所以加載圖片也肯定沒有問題蚂斤,不會拋出錯誤,那么使用try!
執(zhí)行這個函數(shù)即寒,返回值為照片了橡淆,而非一個可選類型召噩。當(dāng)然馬有失蹄,人有失足逸爵,萬一你還是將鏈接地址寫錯了具滴,必定會拋出錯誤,而你又使用了try!
师倔,不好意思构韵,程序崩潰!所以在使用try!
時切記不可馬虎趋艘。
總結(jié)
初稿疲恢,之后進(jìn)行內(nèi)容補充以及修改。