【Swift 3.1】18 - 錯誤處理 (Error Handling)

【Swift 3.1】18 - 錯誤處理 (Error Handling)

自從蘋果2014年發布Swift,到現在已經兩年多了,而Swift也來到了3.1版本。去年利用工作之余,共花了兩個多月的時間把官方的Swift編程指南看完。現在整理一下筆記,回顧一下以前的知識。有需要的同學可以去看官方文檔>>


錯誤處理是在程序中響應錯誤條件和恢復錯誤條件的過程。

表示和拋出錯誤 (Representing and Throwing Errors)

在Swift中,錯誤是用遵循了Error協議的類型的值來表示。枚舉非常適合用來封裝相關的錯誤。

enum VendingMachineError: Error {
    case invalidSelection
    case insufficientFunds(coinsNeeded: Int)
    case outOfStock
}

是用throw來拋出錯誤:

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

處理錯誤 (Handling Errors)

當錯誤拋出后,一些相關的代碼必須處理錯誤,例如改正錯誤、嘗試另外一種辦法或者告知用戶有錯誤。

在Swift中,有四種方法來處理錯誤。把錯誤傳遞給調用這個方法的代碼;使用do-catch語句處理錯誤;把錯誤處理為一個可選類型的值;或者斷言這個錯誤不會發生。下面會演示這四個方法。

當一個方法拋出了錯誤,它會改變程序的流程,所以及時發現錯誤的位置非常重要。為了發現錯誤的位置,在調用方法或初始化器的代碼前使用trytry?或者try!

使用拋出方法來傳遞錯誤 (Propagating Errors Using Throwing Functions)

為了表示一個方法或初始化器可以拋出異常,在方法參數后面加上throw關鍵字:

func canThrowErrors() throws -> String
func cannotThrowErrors() -> String

注意:只有拋出方法才能傳遞錯誤,不能拋出錯誤的方法只能在方法內處理錯誤。

下面是一個例子:

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 vend(itemNamed name: String) throws {
        guard let item = inventory[name] else {
            throw VendingMachineError.invalidSelection
        }
        
        guard item.count > 0 else {
            throw VendingMachineError.outOfStock
        }
        
        guard item.price <= coinsDeposited else {
            throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
        }
        
        coinsDeposited -= item.price
        
        var newItem = item
        newItem.count -= 1
        inventory[name] = newItem
        
        print("Dispensing \(name)")
    }
}

vend(itemNamed:)方法的實現中,使用guard語句來拋出錯誤。

因為vend(itemNamed:)把錯誤傳出去了,所以調用這個方法的代碼必須處理錯誤:使用do-catch語句、try?、try!或者繼續把錯誤傳出去。下面演示的是用繼續把錯誤傳出去的方法來處理:

let favoriteSnacks = [
    "Alice": "Chips",
    "Bob": "Licorice",
    "Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}

因為vend(itemNamed:)能拋出錯誤,所以能再前面加上try

初始化器也能拋出錯誤:

struct PurchasedSnack {
    let name: String
    init(name: String, vendingMachine: VendingMachine) throws {
        try vendingMachine.vend(itemNamed: name)
        self.name = name
    }
}
使用Do-Catch來處理錯誤 (Handling Errors Using Do-Catch)

do-catch語句的通用形式:

do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
}

catch后面寫一個樣式來提示什么樣的錯誤能被這個catch語句處理。如果catch語句沒有樣式,那么這個語句會匹配任何錯誤,并且綁定這個錯誤作為一個本地常量error

catch語句不必處理每一個可能的錯誤。如果沒有catch語句處理這個錯誤,那么這個錯誤將會傳遞給周圍——要么用do-catch語句處理,要么通過內部的拋出方法。例如下面這個例子除了VendingMachineError的所有枚舉值,但其他錯誤只能由周圍的代碼去處理:

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack(person: "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."
把錯誤轉換為可選值 (Converting Error to Optional Values)

使用try?把錯誤轉為可選值。在使用try?的語句中,如果有錯誤拋出,那么這個語句的值為nil

例如下面這個例子,x和y的值相同:

func someThrowingFunction() throws -> Int {
    // ...
}
 
let x = try? someThrowingFunction()
 
let y: Int?
do {
    y = try someThrowingFunction()
} catch {
    y = nil
}

當用同一個方法來處理所有錯誤時,使用try?能是代碼更簡潔:

func fetchData() -> Data? {
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}
禁用錯誤傳遞 (Disabling Error Propagation)

有時候我們知道一個能拋出錯誤的方法在運行過程中時間上不會拋出錯誤。在這種情況下,我們可以在語句前使用try!來禁用錯誤傳遞,并且可以封裝在斷言內,如果真的有錯誤拋出,那么程序報運行時錯誤。

例如:

let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")

指定清理操作 (Specifying Cleanup Actions)

使用defer語句在代碼執行離開當前代碼塊之前執行一些語句。不管代碼執行如何離開當前代碼塊,不管是因為報錯、return或者breakdefer中的語句都能讓我們做一些必要的清理。例如,可以使用defer語句來保證文件描述符被關閉和手動分配的內存被釋放。

defer語句直到當前代碼塊退出時才會執行。不能包括轉移控制語句,例如break或者return,或者拋出一個錯誤。延遲操作將按照他們定義的順序的反序執行,也就是說,第一個defer語句在第二個defer語句之后執行。

func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // Work with the file.
        }
        // close(file) is called here, at the end of the scope.
    }
}

上面這個例子使用defer語句來保證open(_:)方法有對應的close(_:)方法。

注意:即使沒有涉及錯誤處理代碼也可以使用defer語句。


第十八部分完。下個部分:【Swift 3.1】19 - 類型轉換 (Type Casting)


如果有錯誤的地方,歡迎指正!謝謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念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

推薦閱讀更多精彩內容