【譯】Swift2 中的錯誤處理:try,catch,do 以及 throw

原文連接:《Error handling in Swift 2: try, catch, do and throw》
譯文原鏈:Swift2 中的錯誤處理:try,catch,do 以及 throwios

若是你已經看了我那篇討論 Swift2 中全部新東西的文章而且想了解更多關於新的錯誤處理系統的東西,這篇文章很是合適。簡單來講,它已經被徹底重寫得現代化,快速和安全,而且除非你只使用 iOS API 的一小部分的話,你須要花些時間來學習一下。程序員

若是你喜歡這篇文章,你可能也會想讀讀這些:算法

過去是怎樣:NSError 和 NSErrorPointer

用於處理錯誤的歷史方法是經過使用一個做爲指針傳遞的 NSError 對象。在 Objective-C 中,它是 NSError*,但在 Swift 中你會看到 NSError? 和 NSErrorPointer。學習

當你調用一個可能失敗的方法,你要傳遞一個空 NSError 做爲參數,若是有問題的話這個參數就會被賦值。這讓方法的返回值是你真正關心的那個數據。例如,在 Swift1.2 中,從硬盤加載一個 NSString 看起來是這樣的:加密

var err: NSError?
let contents = NSString(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: &err)

if err != nil {
    // uh-oh!
}

這種編程風格在 Cocoa 中很是普遍,或者說至少:Swift2 徹底不這麼幹,因此上面的代碼要麼要重寫要麼就移除掉。es5

至於爲何,有不少緣由。例如,用上面的調用方法,很容易就忽略了錯誤,要麼是沒有檢查 err 的值,要麼是根本就沒用 NSError 而是直接傳遞了一個 nil。

雖然 Swift2 中的新錯誤處理須要多費點功夫,可是它讓程序員閱讀起來清楚明白得多,它拋棄了那些複雜的東西例如用 & 來傳遞 NSError,而且它經過保證你捕獲全部錯誤來給你更高的安全性。

Swift2 中的方法:try,catch,do 以及 throw

當你導入一個 Swift1.2 項目到 Xcode7 時,你會被問道是否想要將它轉換成最新的 Swift 語法。它並不能生成和你手寫的如出一轍的代碼,可是它能幫你解決很大一部分工做,這樣你就差很少肯定要去使用它了。

在上面那個從文件中加載字符串的例子中,它會將其轉化爲 Swift2 版本:

let contents: NSString?
do {
    contents = try NSString(contentsOfFile: filePath, encoding: NSUTF8StringEncoding)
} catch _ {
    contents = nil
}

這裏展現了五個你須要學習的新關鍵字其中三個。固然,嚴格來講有一個不新,不過它的用法是新的:do 以前使用在 do … while 循環中,不過爲了不混淆,在 Swift2 中,它已經被重命名爲 repeat … while。

第四和第五個關鍵字是 throw 和 throws,咱們如今來更深刻地看看。

請建立一個新的 Xcode 項目,用單視圖應用模板。隨便命個名,隨便選個目標設備 - 都不要緊,由於咱們此次不作任何跟視圖有關的東西。

選擇 ViewController.swift 而且添加這個新方法:

func encryptString(str: String, withPassword password: String) -> String {
    // complicated encryption goes here
    let encrypted = password + str + password
    return String(encrypted.characters.reverse())
}

這個方法會用傳遞進來的密碼加密一個字符串。固然,它不會自動就這樣作 - 這篇文章不是關於加密的,因此個人『加密』算法很悲劇:它將密碼添加在輸入的字符串先後,而後翻轉這個字符串。你以後能夠隨意加上覆雜的加密算法。

修改 viewDidLoad() 來調用這個方法:

let encrypted = encryptString("secret information!", withPassword: "12345")
print(encrypted)

你如今運行你的應用,你將看到在 Xcode 終端上打印出了『54321!noitamrofni terces54321』。很簡單對吧。

可是有一個問題:假設你實際上設定了一個有意義的加密算法,你沒辦法阻止用戶輸入一個空字符串做爲密碼,或者輸入明顯的密碼相似『password』,或者甚至嘗試在沒有任何可加密數據的狀況下調用加密算法。

Swift2 來幫忙了:你能夠告訴 Swift 當這個方法發現它本身處於一個不可接受的狀態時,它能夠拋出一個錯誤,例如若是密碼是六位或者更少位。這些錯誤是由你定義的,而後 Swift 用某種辦法來保證你捕獲全部的錯誤。

首先,咱們須要關鍵字 throws,你須要在定義你的方法時把它加在返回值前面,就像這樣:

func encryptString(str: String, withPassword password: String) throws -> String {
    // complicated encryption goes here
    let encrypted = password + str + password
    return String(encrypted.characters.reverse())
}

一旦你這樣作了,你的代碼就會中止工做:添加 throws 命名讓狀況更糟了!不過它變糟了是由於一個好緣由:Swift 中的 try/catch 系統被設計爲對開發者清晰明瞭,這意味着你須要用關鍵字 try 標記全部能夠拋出錯誤的方法,就像這樣:

let encrypted = try encryptString("secret information!", withPassword: "12345")

…不過即便如今你的代碼仍是不能編譯成功,由於你尚未告訴 Swift 當錯誤被拋出時要作什麼。這就是關鍵字 do 和 catch 派上用場的地方:它們開始了一段可能運行失敗的代碼,而且處理那些失敗。在咱們的簡單例子裏,它可能看起來是這樣:

do {
    let encrypted = try encryptString("secret information!", withPassword: "12345")
    print(encrypted)
} catch {
    print("Something went wrong!")
}

這樣全部的錯誤都沒了,你的代碼又能夠運行了。不過目前爲止它實際上尚未作任何有意思的事情,由於即便咱們說 encryptString() 可能拋出一個錯誤,它從沒有真正發生。

如何在 Swift2 中拋出錯誤

在你能夠拋出一個錯誤以前,你須要製做一個你要拋出的可能錯誤的列表。在咱們這個例子中,咱們要組織人們提供空密碼,短密碼和明顯密碼,不過以後你能夠擴展它。

要作到這些,咱們須要建立一個枚舉類型變量來表明咱們錯誤的類型。這須要創建在內建的 ErrorType 枚舉類型上,不過無論怎樣都很簡單。把這個加載 ViewController 類的前面:

enum EncryptionError: ErrorType {
    case Empty
    case Short
}

它定義了兩個錯誤類型,而後咱們能夠立刻開始用它們。由於它們是運行這個方法的前提條件,咱們要用這個新關鍵字 guard 來使咱們的意圖清晰。

把這個放在 encryptString() 前面:

guard password.characters.count > 0 else { throw EncryptionError.Empty }
guard password.characters.count >= 5 else { throw EncryptionError.Short }

若是你如今運行應用,沒有什麼變化,由於咱們在提供『12345』這個密碼。不過若是你把它設置爲一個空字符串,你會看到『Something went wrong!』在 Xcode 控制檯打印出來了。

固然,有一個錯誤信息幫助不是很大 - 由於這個方法調用時有多種方式失敗,而且咱們但願給每一種狀況提供一些有意義的信息。因此,把 viewDidLoad() 中的 try/catch 代碼塊改爲這樣:

do {
    let encrypted = try encryptString("secret information!", withPassword: "")
    print(encrypted)
} catch EncryptionError.Empty {
    print("You must provide a password.")
} catch EncryptionError.Short {
    print("Passwords must be at least five characters, preferably eight or more.")
} catch {
    print("Something went wrong!")
}

如今有了有意義的錯誤信息,咱們的代碼開始看起來更棒了。不過你可能注意到了,雖然咱們已經捕獲到了 .Empty 和 .Short 的狀況,咱們還須要第三個 catch 代碼塊。

Swift2 要求詳盡無遺的 try/catch 錯誤處理

若是你還記得的話,我說過『Swift 經過一些方式來保證你捕獲到全部錯誤』,這裏咱們來講明清楚:咱們已經能捕獲全部咱們定義的錯誤,可是 Swift 還但願咱們定義一個通常的 catch all 來處理任何其餘可能出現的錯誤。咱們不用告訴 Swift 到底加密算法可能拋出哪一種錯誤,只須要說明它會拋出某些錯誤,所以這個額外的 catch-all 代碼塊是必須的。

有一個很差的地方:若是你新增任何值給枚舉類型,它會直接進到默認的 catch 代碼塊 - 你不會被要求爲它提供任何代碼。

咱們將要給枚舉類型加一個新的值來檢測明顯的密碼。不過咱們將要用 Swift 超強枚舉類型這樣咱們能夠返回一個帶着錯誤類型的信息。所以,將 EncryptionError 枚舉類型修改爲這樣:

enum EncryptionError: ErrorType {
    case Empty
    case Short
    case Obvious(String)
}

如今當你想要拋出一個 EncrytionError.Obvious 類型的錯誤是,你必須提供一個理由。

guard password != "12345" else { throw EncryptionError.Obvious("I've got the same passcode on my luggage!") }

顯然你不想寫無數個 guard 聲明來過濾出明顯的密碼,不過若是你記得如何使用 UITextChecker 來作拼寫檢查的話,就很方便了。

這就是完整的 Swift 基本 do/try/throw/catch 例子。你可能以爲 try 聲明沒什麼用,不過他是做爲一個信號告訴開發者『這個調用可能失敗』。這很重要:當一個 try 調用失敗了,執行馬上跳轉到 catch 代碼塊,所以若是你看到一個調用以前的 try,它標誌着底下的代碼可能不會被執行。

還有一個要說的事情就是,若是你知道一個調用就是不會失敗你該怎麼作。如今,很顯然這是一個你須要根據狀況來作的決定,不過若是你知道有一個方法絕對不可能調用失敗或者若是它調用失敗的你的代碼就會徹底崩潰,你可使用 try! 來告訴 Swift。

當你使用關鍵字 try!,你不須要用 do/catch 來包裹你的代碼,由於你在保證它永遠不會失敗。你只須要這樣寫:

let encrypted = try! encryptString("secret information!", withPassword: "12345")
print(encrypted)

使用關鍵字 try! 清楚地表達了你的意圖:你知道理論上這個調用可能失敗,可是你肯定它在你的用例中不會失敗。例如,若是你從你的應用包中的文件中加載內容,任何失敗意味着你的應用包被損壞了或者不可用,因此你須要終止應用。

這就是全部關於 Swift2 中錯誤處理的東西。若是你想學習 Swift 是怎樣處理 try/finally,你應該讀讀我這篇關於關鍵詞 defer 的文章

相關文章
相關標籤/搜索