Swift 對 Error Handling詳解
跟其它語言一樣,Swift的異常處理是在程序拋出異常后的處理邏輯。 Swift提供了一流的異常拋出、捕獲和處理的能力。跟Java語言類似, Swift的異常並不是真正的程序崩潰, 而是程序運行的一個邏輯分支;Swift和Java捕獲異常的時序也是一樣的。當Swift運行時拋出異常后並沒有被處理, 那么程序就會崩潰。
在Swift語言中使用Error表示異常, 作用同Java的Exception類或Object-C的NSError類。 蘋果建議使用枚舉作為異常類型(為什么不推薦用類或者結構體?答案是枚舉數據類型本身就是分成若干種情況,很適合做邏輯分支判斷條件)。
enum VendingMathineError: Error { case invalidSelection case insufficientFunds(coinsNeed: Int) case outOfStack }
上面聲明了枚舉類型VendingMathineError,繼承於Error。 注意Swift的所有異常類型都繼承於Error, 就像Java所有異常類都繼承於Exception一樣。
類似於Java處理異常的try/catch/finally, Swift提供了try、try?、try!、catch、throw、throws關鍵字處理異常邏輯,用法跟Java也很像。
如何聲明一個可能拋出異常的函數? 在函數參數括號后面添加throws關鍵字, 跟Java語法有點像;區別是Swift的throws后面不用跟着異常類、而Java的throws后面要有異常類名稱。 你只要告訴Swift這個函數可能拋出異常就夠了,不需要說明到底是哪種異常,函數體內可以拋出任意類型的異常(肯定是繼承於Error)。
func canThrowErrors() throws -> String
func canThrowErrors(type: Int) throws -> String? { //函數體寫成switch/case更好一些 if type == 1 { throw VendingMathineError.invalidSelection } if type == 2 { throw VendingMathineError.outOfStack } if type == 3 { throw VendingMathineError.insufficientFunds(coinsNeed: 100) } return "success" }
上面測試代碼是為了測試拋異常邏輯, 函數體寫成switch/case更好一些。 從canThrowErrors函數看出,當參數為1、2或3時會拋異常, 語法是throw ... 且程序會跳出函數體,語法同Java。
Swift提供了一種類似於Java try/catch的語法, 是do(函數體內必須有try且該語句可能拋出異常)、catch。
do { try expression statements } catch pattern 1 { statements } catch pattern 2 where condition { statements }
注意:如果try語句拋出異常則會跳出do代碼塊,並按順序逐個catch,當一個catch捕獲成功后,后面的catch不再執行。
do { var data = try canThrowErrors(type: 3)//執行這個函數 這個函數可能拋出異常 print("after execute canThrowErrors") if data != nil { print("Error test data:\(data)") } } catch VendingMathineError.outOfStack { print("outOfStack") } catch VendingMathineError.invalidSelection { print("invalidSelection") } catch { //類似於Java的catch(Exception ex) print("Error") }
輸出:Error
try canThrowsErrors(type: 3)會拋出VendingMathineError.isSufficientFunds(coinsNeed:100),不屬於前2個catch類型, 而最后一個catch是捕獲所有異常, 所有會執行其函數體。
是不是感覺少了點什么? 對, 少了個類似於Java的finally,后面會介紹。
下面再介紹一下try?和try!的用法。
let x = try? someThrowingFunction() //與下面的相同 let y: Int? do{ y = try someThrowingFunction() }catch { y = nil }
try?后面的語句可能會拋出異常, 如果拋出異常則賦值nil給左側;如果沒拋出異常則將返回值賦給左側;
try!取消異常捕獲邏輯,語法有點任性,相當於裸奔, 明知可能拋出異常,但自信這段代碼不會拋異常。 try!是try?的補充。你確定后面語句不會拋出異常,但真的拋出異常后程序會崩潰。不建議使用try!,有使用場景推薦使用try?
let tmpX = try? canThrowErrors(type: 1) //如果拋出異常程序正常運行並賦值nil給左側, 如果沒拋異常則將返回值賦給左側 //let tmpY = try! canThrowErrors(type: 2) //你很確定不會拋出異常時這樣用,但如果運行時拋異常會導致程序崩潰
Swift使用defer關鍵字作用同Java的finally, 即使代碼塊內有break、continue、return或者拋異常,在退出代碼塊后仍然會執行defer代碼塊
下面代碼只是為了測試,驗證函數體內拋出異常時的執行時序, 語法邏輯跟finally一模一樣。
func testDefer(_ param: Int) throws -> String { print("testDefer begin")
defer {//拋出異常就執行defer 為拋出異常就最后執行defer print("testDefer exit") }
// do something...
if param == 1 { throw VendingMathineError.invalidSelection } print("testDefer end") return "testDefer return" } //調用函數 該函數拋出異常tmpZ = nil let tmpZ = try? testDefer(1)
輸出:
testDefer begin
testDefer exit
沒有 defer 拋出異常的執行邏輯
func testDeferNormal() { print("testDefer begin") defer { print("testDefer exit") } print("testDefer end") } testDeferNormal()