本文翻譯自Error Handling in Swift 2.0
Swift主設(shè)計(jì)師在蘋(píng)果今年的WWDC上發(fā)布Swift 2.0時(shí)指出2.0版本主要提升了語(yǔ)言的三個(gè)方面:fundamentals,safety和beautiful code.除去新特性度陆、改進(jìn)、美化等,其中一條對(duì)你的swift 1.x 代碼有影響的要數(shù)錯(cuò)誤處理了.
因?yàn)槟銦o(wú)法不寫(xiě)錯(cuò)誤處理.想要使用Swift 2.0則必須要有它.而且它和以前在Cocoa和Cocoa Touch中使用NSError方法有所不同.
- 歷史起源
眾所周知,Swift是被用來(lái)替代Objective-C開(kāi)發(fā)OS X和iOS應(yīng)用的.在早期的版本中,Objective-C并沒(méi)有原生的錯(cuò)誤處理.后來(lái)有了NSException類(lèi)和NS_DURING,NS_HANDLER,NS_ENDHANDLER宏出現(xiàn)時(shí)才有了異常處理.這套方案被稱(chēng)為"經(jīng)典的異常處理",且這些宏是以C函數(shù)中的setjmp()和longjmp()為基礎(chǔ).
捕捉異常的結(jié)構(gòu)如下所示,其中NS_DURING和NS_HANDLER宏內(nèi)拋出的異常會(huì)執(zhí)行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
快速獲取異常的方法為(現(xiàn)在仍可用):
- (void)someRiskyMethod
{
[NSException raise:@"Kablam"
format:@"This method is not implemented yet. Do not call!"];
}
如你所料,這樣的異常處理方式給早期的Cocoa開(kāi)發(fā)者帶來(lái)很多測(cè)試工作.然而這些程序員仍然可以保持高下巴,因?yàn)樗麄兒苌儆玫剿?在Cocoa和Cocoa Touch中,異常處理被降級(jí)到用來(lái)標(biāo)記災(zāi)難性的、不可恢復(fù)的錯(cuò)誤,例如程序員犯的錯(cuò)誤.上面的-someRiskyMethod就是個(gè)好的例子,是因?yàn)閷?shí)現(xiàn)還沒(méi)準(zhǔn)備好而引發(fā)的一個(gè)異常.Cocoa和Cocoa Touch框架里,用NSError處理的可恢復(fù)錯(cuò)誤會(huì)在待會(huì)討論到.
- 原生異常處理
我想蘋(píng)果從Objective-C經(jīng)典的異常處理中嘗到了苦頭,于是在OS X 10.3時(shí)發(fā)布了原生異常處理,早于iOS的任何版本.它是通過(guò)基本嫁接C++異常到Objective-C中所完成的.異常處理結(jié)構(gòu)現(xiàn)在看起來(lái)是下面的樣子:
@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.
}
原生異常處理通過(guò)不同的@catch blocks來(lái)處理不同的異常類(lèi)型.而無(wú)論@try block里的結(jié)果如何都會(huì)執(zhí)行@finally block里的代碼.
雖然提高NSException的使用可以達(dá)到同原生異常處理同樣的效果,但通過(guò)@throw<expression>的方式使拋出的異常更加明確.而NSException的異常卻是由眾多原因?qū)е碌?
- NSError
雖然原生的異常處理相較于傳統(tǒng)的異常處理有諸多優(yōu)勢(shì),但Cocoa和Cocoa Touch開(kāi)發(fā)者仍然很少用到,僅在不可恢復(fù)的編程錯(cuò)誤時(shí)使用.可恢復(fù)的編程錯(cuò)誤,早期時(shí)用NSError來(lái)處理異常.NSError模式在Swift 1.x時(shí)仍可用.
在Swift 1.x中,Cocoa和Cocoa Touch函數(shù)和方法可能會(huì)失敗,通過(guò)給對(duì)象返回一個(gè)布爾值false或者nil來(lái)表示失敗.此外,NSErrorPointer被當(dāng)做參數(shù)傳遞到返回的錯(cuò)誤信息中.一個(gè)典型的例子如下:
// 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!)")
}
編程錯(cuò)誤可以被標(biāo)記為Swift標(biāo)準(zhǔn)庫(kù)中的fatalError("Error message")方法來(lái)將生成的錯(cuò)誤信息log到控制臺(tái)并無(wú)條件終止執(zhí)行.另外還包括assert(),assertionFailure(),precondition()和preconditionFailure()方法.
當(dāng)Swift首次發(fā)布時(shí),一些Apple平臺(tái)之外的開(kāi)發(fā)者煽風(fēng)點(diǎn)火.他們聲稱(chēng)Swift并不能成為真正的語(yǔ)言,因?yàn)樗狈Ξ惓L幚頇C(jī)制.當(dāng)然,Cocoa和Cocoa Touch開(kāi)發(fā)者知道,有NSError和NSException可用.私以為,蘋(píng)果還在思考實(shí)現(xiàn)error/exception處理的正確方式.且蘋(píng)果推遲了Swift的開(kāi)源,直到問(wèn)題解決.這些障礙隨著Swift 2.0的發(fā)布而被清除.
- Swift 2.0中的錯(cuò)誤處理
如果你想在Swift 2.0中拋出個(gè)錯(cuò)誤,這個(gè)對(duì)象必須遵從ErrorType協(xié)議.如你所想,NSError遵從這個(gè)協(xié)議.使用枚舉來(lái)定義不同的錯(cuò)誤類(lèi)型.
enum AwfulError: ErrorType {
case Bad
case Worse
case Terrible
}
繼而,一個(gè)函數(shù)或方法通過(guò)關(guān)鍵字throws標(biāo)記處理拋出的一個(gè)或多個(gè)錯(cuò)誤:
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()
}
為了獲得這些錯(cuò)誤,一個(gè)新的do-catch語(yǔ)句出現(xiàn)了:
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語(yǔ)句有點(diǎn)像switch,通過(guò)詳盡的引起錯(cuò)誤的清單來(lái)捕捉相對(duì)應(yīng)的錯(cuò)誤類(lèi)型.另外注意try關(guān)鍵字的使用,用來(lái)標(biāo)記拋出異常的代碼位置,以使你在閱讀代碼的時(shí)候知道哪處代碼容易出錯(cuò).
關(guān)鍵字try的另一種用法為try!.該關(guān)鍵字適用于程序員的再次錯(cuò)誤.如果你標(biāo)記一個(gè)異常call為try!,表示告訴編譯器這個(gè)錯(cuò)誤將永不會(huì)出現(xiàn),不需要捕捉到.如果在此出現(xiàn)了錯(cuò)誤,則程序?qū)⑼V惯\(yùn)行,你需要開(kāi)始debug.
let theResult = try! obj.doDangerousStuff()
- 與Cocoa和Cocoa Touch框架的交互
現(xiàn)在的問(wèn)題是,如何在Swift 2.0中處理爺爺輩的NSError API?蘋(píng)果已經(jīng)在Swift 2.0中做了統(tǒng)一的工作,并且做好了將來(lái)用Swift寫(xiě)框架的準(zhǔn)備.
Cocoa和Cocoa Touch函數(shù)和方法產(chǎn)生的NSError時(shí)會(huì)自動(dòng)轉(zhuǎn)換為Swift新的錯(cuò)誤處理機(jī)制.
例如,下面的NSString的初始化在Swift 1.x中有以下signature:
convenience init?(contentsOfFile path: String,
encoding enc: UInt,
error error: NSErrorPointer)
Swift 2.0中signature轉(zhuǎn)換為:
convenience init(contentsOfFile path: String,
encoding enc: UInt) throws
注意到在Swift 2.0中,初始化不再被標(biāo)記為可以失敗,且不需要一個(gè)NSErrorPointer參數(shù),并有個(gè)throws來(lái)標(biāo)記可能存在錯(cuò)誤.使用新的signature的例子如下:
do {
let str = try NSString(contentsOfFile: "Foo.bar",
encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
print(error.localizedDescription)
}
可以看到錯(cuò)誤發(fā)生時(shí)產(chǎn)生了一個(gè)NSError信息,此時(shí)你可以使用你所熟知的API了.實(shí)際上,任何ErrorType都能被轉(zhuǎn)換為NSError.
- 最后來(lái)談?wù)凘finally?
細(xì)心的讀者也許注意到在Swift 2.0中介紹的新的語(yǔ)句為do-catch,而不是do-catch-finally.當(dāng)你需要執(zhí)行不論是否發(fā)生錯(cuò)誤都要執(zhí)行的語(yǔ)句時(shí)怎么辦?為了解決這個(gè)問(wèn)題,現(xiàn)在你可以使用defer語(yǔ)句來(lái)延遲執(zhí)行一個(gè)block代碼直到當(dāng)前作用域退出.
// 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錯(cuò)誤處理外使其進(jìn)入了一個(gè)使眾多開(kāi)發(fā)者更加熟悉的現(xiàn)代化的操作方式.統(tǒng)一的操作方式使Swift語(yǔ)言和它所繼承的框架能夠獲得良好的發(fā)展.
Girl學(xué)iOS100天 第22天