本文翻譯自Error Handling in Swift 2.0
Swift主設計師在蘋果今年的WWDC上發布Swift 2.0時指出2.0版本主要提升了語言的三個方面:fundamentals,safety和beautiful code.除去新特性、改進、美化等,其中一條對你的swift 1.x 代碼有影響的要數錯誤處理了.
因為你無法不寫錯誤處理.想要使用Swift 2.0則必須要有它.而且它和以前在Cocoa和Cocoa Touch中使用NSError方法有所不同.
- 歷史起源
眾所周知,Swift是被用來替代Objective-C開發OS X和iOS應用的.在早期的版本中,Objective-C并沒有原生的錯誤處理.后來有了NSException類和NS_DURING,NS_HANDLER,NS_ENDHANDLER宏出現時才有了異常處理.這套方案被稱為"經典的異常處理",且這些宏是以C函數中的setjmp()和longjmp()為基礎.
捕捉異常的結構如下所示,其中NS_DURING和NS_HANDLER宏內拋出的異常會執行NS_HANDEL和NS_ENDHANDLER宏之間的代碼.
NS_DURING
// Call a dangerous method or function that raises an exception:
[obj someRiskyMethod];
NS_HANDLER
NSLog(@"Oh no!");
[anotherObj makeItRight];
NS_ENDHANDLER
快速獲取異常的方法為(現在仍可用):
- (void)someRiskyMethod
{
[NSException raise:@"Kablam"
format:@"This method is not implemented yet. Do not call!"];
}
如你所料,這樣的異常處理方式給早期的Cocoa開發者帶來很多測試工作.然而這些程序員仍然可以保持高下巴,因為他們很少用到它.在Cocoa和Cocoa Touch中,異常處理被降級到用來標記災難性的、不可恢復的錯誤,例如程序員犯的錯誤.上面的-someRiskyMethod就是個好的例子,是因為實現還沒準備好而引發的一個異常.Cocoa和Cocoa Touch框架里,用NSError處理的可恢復錯誤會在待會討論到.
- 原生異常處理
我想蘋果從Objective-C經典的異常處理中嘗到了苦頭,于是在OS X 10.3時發布了原生異常處理,早于iOS的任何版本.它是通過基本嫁接C++異常到Objective-C中所完成的.異常處理結構現在看起來是下面的樣子:
@try {
[obj someRiskyMethod];
}
@catch (SomeClass *exception) {
// Handle the error.
// Can use the exception object to gather information.
}
@catch (SomeOtherClass *exception) {
// ...
}
@catch (id allTheRest) {
// ...
}
@finally {
// Code that is executed whether an exception is thrown or not.
// Use for cleanup.
}
原生異常處理通過不同的@catch blocks來處理不同的異常類型.而無論@try block里的結果如何都會執行@finally block里的代碼.
雖然提高NSException的使用可以達到同原生異常處理同樣的效果,但通過@throw<expression>的方式使拋出的異常更加明確.而NSException的異常卻是由眾多原因導致的.
- NSError
雖然原生的異常處理相較于傳統的異常處理有諸多優勢,但Cocoa和Cocoa Touch開發者仍然很少用到,僅在不可恢復的編程錯誤時使用.可恢復的編程錯誤,早期時用NSError來處理異常.NSError模式在Swift 1.x時仍可用.
在Swift 1.x中,Cocoa和Cocoa Touch函數和方法可能會失敗,通過給對象返回一個布爾值false或者nil來表示失敗.此外,NSErrorPointer被當做參數傳遞到返回的錯誤信息中.一個典型的例子如下:
// A local variable to store an error object if one comes back:
var error: NSError?
// success is a Bool:
let success = someString.writeToURL(someURL,
atomically: true,
encoding: NSUTF8StringEncoding,
error: &error)
if !success {
// Log information about the error:
println("Error writing to URL: \(error!)")
}
編程錯誤可以被標記為Swift標準庫中的fatalError("Error message")方法來將生成的錯誤信息log到控制臺并無條件終止執行.另外還包括assert(),assertionFailure(),precondition()和preconditionFailure()方法.
當Swift首次發布時,一些Apple平臺之外的開發者煽風點火.他們聲稱Swift并不能成為真正的語言,因為它缺乏異常處理機制.當然,Cocoa和Cocoa Touch開發者知道,有NSError和NSException可用.私以為,蘋果還在思考實現error/exception處理的正確方式.且蘋果推遲了Swift的開源,直到問題解決.這些障礙隨著Swift 2.0的發布而被清除.
- Swift 2.0中的錯誤處理
如果你想在Swift 2.0中拋出個錯誤,這個對象必須遵從ErrorType協議.如你所想,NSError遵從這個協議.使用枚舉來定義不同的錯誤類型.
enum AwfulError: ErrorType {
case Bad
case Worse
case Terrible
}
繼而,一個函數或方法通過關鍵字throws標記處理拋出的一個或多個錯誤:
func doDangerousStuff() throws -> SomeObject {
// If something bad happens throw the error:
throw AwfulError.Bad
// If something worse happens, throw another error:
throw AwfulError.Worse
// If something terrible happens, you know what to do:
throw AwfulError.Terrible
// If you made it here, you can return:
return SomeObject()
}
為了獲得這些錯誤,一個新的do-catch語句出現了:
do {
let theResult = try obj.doDangerousStuff()
}
catch AwfulError.Bad {
// Deal with badness.
}
catch AwfulError.Worse {
// Deal with worseness.
}
catch AwfulError.Terrible {
// Deal with terribleness.
}
catch ErrorType {
// Unexpected error!
}
do-catch語句有點像switch,通過詳盡的引起錯誤的清單來捕捉相對應的錯誤類型.另外注意try關鍵字的使用,用來標記拋出異常的代碼位置,以使你在閱讀代碼的時候知道哪處代碼容易出錯.
關鍵字try的另一種用法為try!.該關鍵字適用于程序員的再次錯誤.如果你標記一個異常call為try!,表示告訴編譯器這個錯誤將永不會出現,不需要捕捉到.如果在此出現了錯誤,則程序將停止運行,你需要開始debug.
let theResult = try! obj.doDangerousStuff()
- 與Cocoa和Cocoa Touch框架的交互
現在的問題是,如何在Swift 2.0中處理爺爺輩的NSError API?蘋果已經在Swift 2.0中做了統一的工作,并且做好了將來用Swift寫框架的準備.
Cocoa和Cocoa Touch函數和方法產生的NSError時會自動轉換為Swift新的錯誤處理機制.
例如,下面的NSString的初始化在Swift 1.x中有以下signature:
convenience init?(contentsOfFile path: String,
encoding enc: UInt,
error error: NSErrorPointer)
Swift 2.0中signature轉換為:
convenience init(contentsOfFile path: String,
encoding enc: UInt) throws
注意到在Swift 2.0中,初始化不再被標記為可以失敗,且不需要一個NSErrorPointer參數,并有個throws來標記可能存在錯誤.使用新的signature的例子如下:
do {
let str = try NSString(contentsOfFile: "Foo.bar",
encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
print(error.localizedDescription)
}
可以看到錯誤發生時產生了一個NSError信息,此時你可以使用你所熟知的API了.實際上,任何ErrorType都能被轉換為NSError.
- 最后來談談@finally?
細心的讀者也許注意到在Swift 2.0中介紹的新的語句為do-catch,而不是do-catch-finally.當你需要執行不論是否發生錯誤都要執行的語句時怎么辦?為了解決這個問題,現在你可以使用defer語句來延遲執行一個block代碼直到當前作用域退出.
// Some scope:
{
// Get some resource.
defer {
// Release resource.
}
// Do things with the resource.
// Possibly return early if an error occurs.
} // Deferred code is executed at the end of the scope.
Swift 2.0在融合以前的Cocoa和Cocoa Touch錯誤處理外使其進入了一個使眾多開發者更加熟悉的現代化的操作方式.統一的操作方式使Swift語言和它所繼承的框架能夠獲得良好的發展.
Girl學iOS100天 第22天