蘋果公司在今年的全球開(kāi)發(fā)者大會(huì)(Worldwide Developers Conference, WWDC)上宣布推出Swift2.0,該語(yǔ)言的首席架構(gòu)師Chris Lattner表示,Swift 2.0主要在語(yǔ)言基本語(yǔ)法、安全性和格式美觀度這三方面進(jìn)行了改進(jìn)。除了這些新的功能特性,還有對(duì)語(yǔ)法的優(yōu)化、修飾及美化,最后是Swift 1.x中最具影響力的錯(cuò)誤處理機(jī)制。
這是因?yàn)槟愀緹o(wú)法回避它。如果打算使用Swift 2.0的話,必須接受錯(cuò)誤處理這樣的機(jī)制,并且錯(cuò)誤處理機(jī)制將改變Cocoa和Cocoa Touch框架中使用NSError與方法交互的方式。
我們都知道,Swift語(yǔ)言作為Objective-C當(dāng)前替代語(yǔ)言被推出,是OS X和iOS應(yīng)用程序開(kāi)發(fā)的“通用語(yǔ)”。在最初的版本中,Objective-C沒(méi)有原生的異常處理機(jī)制。后來(lái)通過(guò)添加NSException類,還有 NS_DURING, NS_HANDLER和 NS_ENDHANDLER宏才有了異常處理。這種方案現(xiàn)在被稱為“經(jīng)典的異常處理”,還有這些宏都是基于setjmp()和longjmp()這兩個(gè)C語(yǔ)言函數(shù)的。
異常捕獲(exception-catching)看起來(lái)如下所示,在NS_DURING和NS_HANDLER宏之間拋出的任何異常都將會(huì)導(dǎo)致在NS_HANDLER和NS_ENDHANDLER宏之間執(zhí)行相應(yīng)的代碼。
NS_DURING // Call a dangerous method or function that raises an exception: [obj someRiskyMethod]; NS_HANDLER NSLog(@"Oh no!"); [anotherObj makeItRight]; NS_ENDHANDLER下面是立刻能觸發(fā)拋出異常的方法(現(xiàn)在仍然可用):
- (void)someRiskyMethod { [NSException raise:@"Kablam" format:@"This method is not implemented yet. Do not call!"]; }
可以想象,這種手工處理異常的方式戲弄的是早期Cocoa框架程序開(kāi)發(fā)人員。但是這些程序員還不至于到這份兒上,因?yàn)樗麄兒苌偈褂眠@種方式。無(wú)論在Cocoa還是Cocoa Touch框架下,異常通常都被歸為災(zāi)難性的,不可恢復(fù)的錯(cuò)誤,比如程序員造成的錯(cuò)誤。上面的-someRiskyMethod就是很好的例子,由于實(shí)現(xiàn)部分沒(méi)有準(zhǔn)備好而引發(fā)了異常。在Cocoa和Cocoa Touch框架中,可恢復(fù)的錯(cuò)誤由稍后討論的NSError類來(lái)處理。
我想由于Objective-C中的經(jīng)典異常處理機(jī)制對(duì)應(yīng)的手工處理方式讓人感覺(jué)鬧心,于是蘋果公司在Mac OS X 10.3(2003年10月)中發(fā)布了原生的異常處理機(jī)制,彼時(shí)還沒(méi)有iOS系統(tǒng)。這本質(zhì)上是將C++的異常處理嫁接到了Objective-C。異常處理的結(jié)構(gòu)目前看起來(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. }
原生的異常處理使你有機(jī)會(huì)為每個(gè)異常類型指定不同@catch部分。無(wú)論@try結(jié)果如何,@finally都要執(zhí)行其對(duì)應(yīng)的代碼。
盡管原生的異常處理如所預(yù)期的那樣拋出一個(gè)NSException異常,但是最明確的方法還是“@throw <expression>;”語(yǔ)句。通常你拋出的是NSException實(shí)例,但說(shuō)不定什么對(duì)象會(huì)被拋出。
盡管Objective-C原生與經(jīng)典的異常處理有許多優(yōu)點(diǎn),但Cocoa和Cocoa Touch框架應(yīng)用程序開(kāi)發(fā)人員仍然很少使用異常,而是限制程序出現(xiàn)程序員所導(dǎo)致的不可恢復(fù)的錯(cuò)誤。使用NSError類處理可恢復(fù)的錯(cuò)誤,這種方法早于使用異常處理。Swift 1.x也繼承了NSError的樣式。
在Swift 1.x中,Cocoa和Cocoa Touch的方法和函數(shù)可能不會(huì)返回一個(gè)布爾類型的false或者nil來(lái)表示一個(gè)失?。╢ailure)的對(duì)象。另外,NSErrorPointer對(duì)象會(huì)被當(dāng)作一個(gè)參數(shù)返回特定的失敗信息。下面是個(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!)") }
程序員所導(dǎo)致的錯(cuò)誤可以用Swift標(biāo)準(zhǔn)庫(kù)(Swift Standard Library)函數(shù)fatalError("Error message”)來(lái)標(biāo)記,將其在控制臺(tái)記錄為錯(cuò)誤消息并無(wú)條件中止執(zhí)行。還可以使用assert(), assertionFailure(), precondition()和preconditionFailure()這些函數(shù)。
Swift第一次發(fā)布時(shí),一些非蘋果平臺(tái)開(kāi)發(fā)人員已經(jīng)準(zhǔn)備好了火把和干草叉。他們聲稱Swift不能算是“真正的語(yǔ)言”,因?yàn)樗狈Ξ惓L幚?。但是,Cocoa和Cocoa Touch社區(qū)對(duì)此不予理睬,我們知道NSError和NSException那個(gè)時(shí)候就存在了。就我個(gè)人而言,我相信蘋果公司仍然在思考實(shí)現(xiàn)錯(cuò)誤和異常處理的正確方式。我還認(rèn)為直到問(wèn)題解決了,蘋果公司才會(huì)公開(kāi)Swift源碼。這一切問(wèn)題在Swift 2.0中全被掃清了。
在Swift 2.0中,如果想要拋出錯(cuò)誤,那么拋出的對(duì)象必須符合ErrorType協(xié)議。可能正如你所愿,NSError就符合該協(xié)議。枚舉在這里用來(lái)給錯(cuò)誤進(jìn)行分類。
enum AwfulError: ErrorType { case Bad case Worse case Terrible }然后如果一個(gè)可能拋出一個(gè)或多個(gè)錯(cuò)誤的函數(shù)或方法會(huì)被throws關(guān)鍵字標(biāo)記:
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ò)誤,新型的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! }
這個(gè)do-catch語(yǔ)句和switch語(yǔ)句有一些相似之處,被捕獲的錯(cuò)誤詳盡無(wú)遺,因此你可以使用這種樣式來(lái)捕獲拋出的錯(cuò)誤。還要注意關(guān)鍵字try的使用。它是為了明確地標(biāo)示拋出的代碼行,因此當(dāng)閱讀代碼的時(shí)候,你能夠立刻找到錯(cuò)誤在哪里。
關(guān)鍵字try的變體是“try!”。這個(gè)關(guān)鍵字大概也適用于那些程序員導(dǎo)致的錯(cuò)誤。如果使用“try!”標(biāo)記一個(gè)被調(diào)用的拋出對(duì)象中的方法,你等于告訴編譯器這個(gè)錯(cuò)誤永遠(yuǎn)不會(huì)發(fā)生,并且你也不需要捕獲它。如果該語(yǔ)句本身產(chǎn)生了錯(cuò)誤(error),應(yīng)用程序會(huì)停止執(zhí)行,那么你就要開(kāi)始調(diào)試了。
let theResult = try! obj.doDangerousStuff()
現(xiàn)在的問(wèn)題是,你如何在Swift 2.0中處理爺爺級(jí)的NSError API呢?蘋果公司已經(jīng)在Swift 2.0中為統(tǒng)一代碼行為作了大量工作,并且已經(jīng)為未來(lái)寫入Swift的框架準(zhǔn)備方法。Cocoa和Cocoa Touch中可以產(chǎn)生NSError實(shí)例的方法和函數(shù)有蘋果公司的簽名( signature),可以自動(dòng)轉(zhuǎn)換為Swift新的錯(cuò)誤處理方式。
例如,這個(gè)NSString的構(gòu)造器( initializer)在Swift 1.x中就有以下簽名:
convenience init?(contentsOfFile path: String, encoding enc: UInt, error error: NSErrorPointer)Swift 2.0中,簽名被轉(zhuǎn)換成:
convenience init(contentsOfFile path: String, encoding enc: UInt) throws
注意:在Swift 2.0中,構(gòu)造器不再被標(biāo)記為failable,它并不需要NSErrorPointer來(lái)做參數(shù),而是使用拋出異常的方式顯式地指示潛在的失敗。
下面的例子使用了這種新的簽名:
do { let str = try NSString(contentsOfFile: "Foo.bar", encoding: NSUTF8StringEncoding) } catch let error as NSError { print(error.localizedDescription) }
注意錯(cuò)誤是如何被捕獲的,并且如何被轉(zhuǎn)換成了一個(gè)NSError實(shí)例,這樣你就可以獲取與其相似API的信息了。事實(shí)上,任何ErrorType類型的實(shí)例都可以轉(zhuǎn)換成NSError類型。
細(xì)心的讀者可能已經(jīng)注意到,Swift 2.0引入了一個(gè)新的do-catch語(yǔ)句,而不是do-catch-finally。不管是否捕捉到錯(cuò)誤的情況下,你如何指定必須運(yùn)行的代碼呢?為此,現(xiàn)在可以使用defer語(yǔ)句,用來(lái)推遲代碼塊的執(zhí)行直到當(dāng)前的作用域結(jié)束。
// 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ī)制凝聚為具有現(xiàn)代風(fēng)格的用法,這是一項(xiàng)偉大的工作,也會(huì)使許多程序員倍感親切。統(tǒng)一行為是不錯(cuò)的定位,會(huì)使Swift語(yǔ)言和其所繼承的框架逐步發(fā)展。
(翻譯/白云鵬 友情審校/汪洋)
文章來(lái)源:Big Nerd Ranch
作者簡(jiǎn)介:Juan Pablo Claude,來(lái)自智利首都圣地亞哥,畢業(yè)于美國(guó)北卡羅萊納大學(xué)教堂山分校(University of North Carolina at Chapel Hill),獲化學(xué)博士學(xué)位,后入阿拉巴馬大學(xué)伯明翰分校(University of Alabama at Birmingham)任教。2005年底作為Cocoa和Django框架程序開(kāi)發(fā)人員加入Big Nerd Ranch。此前,有過(guò)DOS環(huán)境下C編程,Windows環(huán)境下使用C++編寫數(shù)據(jù)分析應(yīng)用程序的經(jīng)歷。
譯者簡(jiǎn)介:白云鵬,移動(dòng)應(yīng)用開(kāi)發(fā)者,個(gè)人博客:http://baiyunpeng.com。