[Swift2.0系列]Error Handling(基礎(chǔ)語法篇)

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)
}
  1. favoriteSnacks是一個字典孟抗,類型為[String:String],鍵對應(yīng)人名钻心,值對應(yīng)顧客喜愛的貨物名字凄硼。
  2. 函數(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塊錢
  1. 首先實例花了一個販賣機vendingMachine
  2. 投入硬幣金額為8元楼咳。
  3. 使用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)容補充以及修改。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓷胧,一起剝皮案震驚了整個濱河市显拳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搓萧,老刑警劉巖杂数,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瘸洛,居然都是意外死亡揍移,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門反肋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來那伐,“玉大人,你說我怎么就攤上這事石蔗『毖” “怎么了?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵抓督,是天一觀的道長燃少。 經(jīng)常有香客問我,道長铃在,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任碍遍,我火速辦了婚禮定铜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怕敬。我一直安慰自己揣炕,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布东跪。 她就那樣靜靜地躺著畸陡,像睡著了一般鹰溜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上丁恭,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天曹动,我揣著相機與錄音,去河邊找鬼牲览。 笑死墓陈,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的第献。 我是一名探鬼主播贡必,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼庸毫!你這毒婦竟也來了仔拟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤飒赃,失蹤者是張志新(化名)和其女友劉穎利花,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盒揉,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡晋被,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了刚盈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片羡洛。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖藕漱,靈堂內(nèi)的尸體忽然破棺而出欲侮,到底是詐尸還是另有隱情,我是刑警寧澤肋联,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布威蕉,位于F島的核電站,受9級特大地震影響橄仍,放射性物質(zhì)發(fā)生泄漏韧涨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一侮繁、第九天 我趴在偏房一處隱蔽的房頂上張望虑粥。 院中可真熱鬧,春花似錦宪哩、人聲如沸娩贷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彬祖。三九已至茁瘦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間储笑,已是汗流浹背甜熔。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留南蓬,地道東北人纺非。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像赘方,于是被迫代替她去往敵國和親烧颖。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容