歡迎加入QQ討論群258996829
贏了現(xiàn)在輸了未來(lái) 頭像
蘋果2袋
2
贏了現(xiàn)在輸了未來(lái)

不算“真正的語(yǔ)言”?詳說(shuō)Swift 2.0中的錯(cuò)誤處理

發(fā)布時(shí)間:2015-07-05 21:46  回復(fù):0  查看:3118   最后回復(fù):2015-07-05 21:46  

蘋果公司在今年的全球開(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與方法交互的方式。

歷史一瞬:不起眼的開(kāi)端

我們都知道,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ì)被拋出。

NSError

盡管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ò)誤處理

在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()

與Cocoa和Cocoa Touch框架間的交互

現(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類型。

最后說(shuō)說(shuō)@finally

細(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

您還未登錄,請(qǐng)先登錄

熱門帖子

最新帖子

?