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

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)
}
  1. favoriteSnacks是一個字典,類型為[String:String],鍵對應人名,值對應顧客喜愛的貨物名字。
  2. 函數傳入參數有兩個,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塊錢
  1. 首先實例花了一個販賣機vendingMachine
  2. 投入硬幣金額為8元。
  3. 使用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!時切記不可馬虎。

總結

初稿,之后進行內容補充以及修改。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容