指南:錯誤處理(Error Handling)

錯誤處理(Error handling)是響應錯誤以及從錯誤中恢復的過程蝉稳。Swift 提供了在運行時對可恢復錯誤的拋出宾添、捕獲增热、傳遞和操作的一流支持障贸。

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

  • 在 Swift 中,錯誤用符合ErrorType協(xié)議的類型的值來表示确垫。這個空協(xié)議表明該類型可以用于錯誤處理弓颈。

  • Swift 的枚舉類型尤為適合構建一組相關的錯誤狀態(tài)帽芽,枚舉的關聯值還可以提供錯誤狀態(tài)的額外信息。

  • 拋出一個錯誤表明有意外情況發(fā)生翔冀,導致正常的執(zhí)行流程無法繼續(xù)執(zhí)行导街。拋出錯誤使用throw關鍵字。

處理錯誤(Handling Errors)

  • 某個錯誤被拋出時纤子,附近的某部分代碼必須負責處理這個錯誤搬瑰,例如糾正這個問題、嘗試另外一種方式控硼、或是向用戶報告錯誤泽论。

  • Swift 中有4種處理錯誤的方式。把函數拋出的錯誤傳遞給調用此函數的代碼象颖、用do-catch語句處理錯誤佩厚、將錯誤作為可選類型處理、或者斷言此錯誤根本不會發(fā)生说订。

  • 當一個函數拋出一個錯誤時抄瓦,程序流程會發(fā)生改變,所以重要的是你能迅速識別代碼中會拋出錯誤的地方陶冷。為了標識出這些地方钙姊,在調用一個能拋出錯誤的函數、方法或者構造器之前埂伦,加上try關鍵字煞额,或者try?或try!這種變體。

用 throwing 函數傳遞錯誤(Propagating Errors Using Throwing Functions)

  • 為了表示一個函數沾谜、方法或構造器可以拋出錯誤膊毁,在函數聲明的參數列表之后加上throws關鍵字。一個標有throws關鍵字的函數被稱作throwing 函數基跑。如果這個函數指明了返回值類型婚温,throws關鍵詞需要寫在箭頭(->)的前面。

  • 只有 throwing 函數可以傳遞錯誤媳否。任何在某個非 throwing 函數內部拋出的錯誤只能在函數內部處理栅螟。

用 Do-Catch 處理錯誤(Handling Errors Using Do-Catch)

  • 可以使用一個do-catch語句運行一段閉包代碼來處理錯誤。如果在do子句中的代碼拋出了一個錯誤篱竭,這個錯誤會與catch子句做匹配力图,從而決定哪條子句能處理它。
do {
    try expression
    statements
} catch pattern 1 {
    statements
} catch pattern 2 where condition {
    statements
}
  • 在catch后面寫一個匹配模式來表明這個子句能處理什么樣的錯誤掺逼。如果一條catch子句沒有指定匹配模式吃媒,那么這條子句可以匹配任何錯誤,并且把錯誤綁定到一個名字為error的局部常量。

  • catch子句不必將do子句中的代碼所拋出的每一個可能的錯誤都作處理晓折。如果所有catch子句都未處理錯誤惑朦,錯誤就會傳遞到周圍的作用域。然而漓概,錯誤還是必須要被某個周圍的作用域處理的——要么是一個外圍的do-catch錯誤處理語句,要么是一個 throwing 函數的內部病梢。

將錯誤轉換成可選值(Converting Errors to Optional Values)

  • 可以使用try?通過將錯誤轉換成一個可選值來處理錯誤胃珍。如果在評估try?
    表達式時一個錯誤被拋出,那么表達式的值就是nil蜓陌。

  • 如果想對所有的錯誤都采用同樣的方式來處理觅彰,用try?就可以寫出簡潔的錯誤處理代碼。

func fetchData() -> Data? {
    if let data = try? fetchDataFromDisk() { return data }
    if let data = try? fetchDataFromServer() { return data }
    return nil
}

禁用錯誤傳遞(Disabling Error Propagation)

  • 有時你知道某個 throwing 函數實際上在運行時是不會拋出錯誤的钮热,在這種情況下填抬,你可以在表達式前面寫try!來禁用錯誤傳遞,這會把調用包裝在一個不會有錯誤拋出的運行時斷言中隧期。如果真的拋出了錯誤飒责,會得到一個運行時錯誤。程序崩潰仆潮。

用try? 代替 try!宏蛉,值可以不用,多寫一行if let也沒什么性置,要防止崩潰

enum VendingMachineError: ErrorType {
    case InvalidSelection                    //選擇無效
    case InsufficientFunds(coinsNeeded: Int) //金額不足
    case OutOfStock                          //缺貨
}

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)")
    }

    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

        dispenseSnack(name)
    }
}

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)
}

struct PurchasedSnack {
    let name: String
    init(name: String, vendingMachine: VendingMachine) throws {
        try vendingMachine.vend(itemNamed: name)
        self.name = name
    }
}

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    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.")
}
// 打印 “Insufficient funds. Please insert an additional 2 coins.”

指定清理操作(Specifying Cleanup Actions)

  • 可以使用defer語句在即將離開當前代碼塊時執(zhí)行一系列語句拾并。該語句讓你能執(zhí)行一些必要的清理工作,不管是以何種方式離開當前代碼塊的——無論是由于拋出錯誤而離開鹏浅,還是由于諸如return或者break的語句嗅义。

  • 例如,可以用defer語句來確保文件描述符得以關閉隐砸,以及手動分配的內存得以釋放之碗。

  • defer語句將代碼的執(zhí)行延遲到當前的作用域退出之前。該語句由defer關鍵字和要被延遲執(zhí)行的語句組成凰萨。

  • 延遲執(zhí)行的語句不能包含任何控制轉移語句继控,例如break或是return語句,或是拋出一個錯誤胖眷。

  • 延遲執(zhí)行的操作會按照它們被指定時的順序的相反順序執(zhí)行——也就是說武通,第一條defer語句中的代碼會在第二條defer語句中的代碼被執(zhí)行之后才執(zhí)行,以此類推珊搀。

  • 即使沒有涉及到錯誤處理冶忱,你也可以使用defer語句。

func processFile(filename: String) throws {
    if exists(filename) {
        let file = open(filename)
        defer {
            close(file)
        }
        while let line = try file.readline() {
            // 處理文件境析。
        }
        // close(file) 會在這里被調用囚枪,即作用域的最后派诬。
    }
}
// 使用一條defer語句來確保open(_:)函數有一個相應的對close(_:)函數的調用。
  • enum和ErroyType配對使用链沼,推薦默赂。拋出異常,指明錯誤原因括勺,在網絡缆八,文件,數據庫等涉及資源相關概念的場景適合使用
  • defer關鍵字疾捍,強烈推薦使用奈辰。文件打開,馬上寫上關閉乱豆,成對出現奖恰。這是一種好的編程習慣。
  • guard和throw配對使用宛裕,推薦瑟啃。先檢查條件,再往下執(zhí)行具體任務续滋。
  • do - try - catch結構和其他的try-catch-finally結構是不一樣的
  • try翰守?是一種簡化寫法,不推薦用疲酌。直接用可選類型的普通函數就好了蜡峰,函數定義時就不要加throw關鍵字了。
  • try朗恳!強烈不推薦使用湿颅,用try?代替
  • Swift整體的錯誤處理的思路還是值得肯定的粥诫。首先油航,錯誤處理拋出異常會打斷程序的正常執(zhí)行順序,所以并不推薦經常使用怀浆,所以要加throw谊囚、try等關鍵字,增加使用麻煩度执赡。其次镰踏,在真正需要使用拋出異常的地方,位置信息更加精確沙合。最后奠伪,將枚舉和協(xié)議的概念引入,讓錯誤信息的表達更加方便。
  • Object-C是使用NSError來處理錯誤的绊率,沒有異常拋出過程谨敛,使用時要傳地址&,這是C語言常見的錯誤處理過程滤否。這樣的好處是程序的執(zhí)行流程不會被打斷脸狸,錯誤信息可以存儲,信息是否使用顽聂,由調用者決定肥惭。
  • 兩種錯誤處理方式各有優(yōu)缺點,在Swift中紊搪,還是推薦Swift特有的錯誤處理方式,還算是比較好用好理解的全景。
  • 一般情況下耀石,可選類型就足夠表達意思了;在需要處理錯誤信息的場合爸黄,才考慮引入錯誤處理機制滞伟。比如文件讀寫,網絡炕贵,數據庫等等梆奈。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市称开,隨后出現的幾起案子亩钟,更是在濱河造成了極大的恐慌,老刑警劉巖鳖轰,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件清酥,死亡現場離奇詭異,居然都是意外死亡蕴侣,警方通過查閱死者的電腦和手機焰轻,發(fā)現死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昆雀,“玉大人辱志,你說我怎么就攤上這事∧欤” “怎么了揩懒?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長客冈。 經常有香客問我旭从,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任和悦,我火速辦了婚禮退疫,結果婚禮上,老公的妹妹穿的比我還像新娘鸽素。我一直安慰自己褒繁,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布馍忽。 她就那樣靜靜地躺著棒坏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遭笋。 梳的紋絲不亂的頭發(fā)上坝冕,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音瓦呼,去河邊找鬼喂窟。 笑死,一個胖子當著我的面吹牛央串,可吹牛的內容都是我干的磨澡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼质和,長吁一口氣:“原來是場噩夢啊……” “哼稳摄!你這毒婦竟也來了?” 一聲冷哼從身側響起饲宿,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤厦酬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后褒傅,有當地人在樹林里發(fā)現了一具尸體弃锐,經...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年殿托,在試婚紗的時候發(fā)現自己被綠了霹菊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡支竹,死狀恐怖旋廷,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情礼搁,我是刑警寧澤饶碘,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站馒吴,受9級特大地震影響扎运,放射性物質發(fā)生泄漏瑟曲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一豪治、第九天 我趴在偏房一處隱蔽的房頂上張望洞拨。 院中可真熱鬧询吴,春花似錦戒悠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽花吟。三九已至,卻和暖如春厨姚,著一層夾襖步出監(jiān)牢的瞬間衅澈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工谬墙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留矾麻,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓芭梯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弄喘。 傳聞我的和親對象是個殘疾皇子玖喘,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容