不算“真正的語言”?詳說Swift 2.0中的錯誤處理

來源:互聯網
上載者:User

標籤:

蘋果公司在今年的全球開發人員大會(Worldwide Developers Conference, WWDC)上宣布推出Swift2.0,該語言的首席架構師Chris Lattner表示,Swift 2.0主要在語言基本文法、安全性和格式美觀度這三方面進行了改進。除了這些新的功能特性,還有對文法的最佳化、修飾及美化,最後是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宏才有了異常處理。這種方案現在被稱為“經典的異常處理”,還有這些宏都是基於setjmp()和longjmp()這兩個C語言函數的。

異常捕獲(exception-catching)看起來如下所示,在NS_DURING和NS_HANDLER宏之間拋出的任何異常都將會導致在NS_HANDLER和NS_ENDHANDLER宏之間執行相應的代碼。

 

[cpp] view plaincopy 
  1. NS_DURING  
  2.     // Call a dangerous method or function that raises an exception:  
  3.     [obj someRiskyMethod];  
  4. NS_HANDLER  
  5.     NSLog(@"Oh no!");  
  6.     [anotherObj makeItRight];  
  7. NS_ENDHANDLER  

 

下面是立刻能觸發拋出異常的方法(現在仍然可用):

 

[cpp] view plaincopy 
  1. - (void)someRiskyMethod  
  2. {  
  3.     [NSException raise:@"Kablam"  
  4.                 format:@"This method is not implemented yet. Do not call!"];  
  5. }  

 

可以想象,這種手工處理異常的方式戲弄的是早期Cocoa架構程式開發人員。但是這些程式員還不至於到這份兒上,因為他們很少使用這種方式。無論在Cocoa還是Cocoa Touch架構下,異常通常都被歸為災難性的,不可恢複的錯誤,比如程式員造成的錯誤。上面的-someRiskyMethod就是很好的例子,由於實現部分沒有準備好而引發了異常。在Cocoa和Cocoa Touch架構中,可恢複的錯誤由稍後討論的NSError類來處理。

原生的異常處理

我想由於Objective-C中的經典異常處理機制對應的手工處理方式讓人感覺鬧心,於是蘋果公司在Mac OS X 10.3(2003年10月)中發布了原生的異常處理機制,彼時還沒有iOS系統。這本質上是將C++的異常處理嫁接到了Objective-C。異常處理的結構目前看起來是這樣的:

 

[cpp] view plaincopy 
  1. @try {  
  2.     [obj someRiskyMethod];  
  3. }  
  4. @catch (SomeClass *exception) {  
  5.     // Handle the error.  
  6.     // Can use the exception object to gather information.  
  7. }  
  8. @catch (SomeOtherClass *exception) {  
  9.     // ...  
  10. }  
  11. @catch (id allTheRest) {  
  12.     // ...  
  13. }  
  14. @finally {  
  15.     // Code that is executed whether an exception is thrown or not.  
  16.     // Use for cleanup.  
  17. }  

 

原生的異常處理使你有機會為每個異常類型指定不同@catch部分。無論@try結果如何,@finally都要執行其對應的代碼。

儘管原生的異常處理如所預期的那樣拋出一個NSException異常,但是最明確的方法還是“@throw <expression>;”語句。通常你拋出的是NSException執行個體,但說不定什麼對象會被拋出。

NSError

儘管Objective-C原生與經典的異常處理有許多優點,但Cocoa和Cocoa Touch架構應用程式開發人員仍然很少使用異常,而是限制程式出現程式員所導致的不可恢複的錯誤。使用NSError類處理可恢複的錯誤,這種方法早於使用異常處理。Swift 1.x也繼承了NSError的樣式。

在Swift 1.x中,Cocoa和Cocoa Touch的方法和函數可能不會返回一個布爾類型的false或者nil來表示一個失敗(failure)的對象。另外,NSErrorPointer對象會被當作一個參數返回特定的失敗資訊。下面是個典型的例子:

 

[cpp] view plaincopy 
  1. // A local variable to store an error object if one comes back:  
  2. var error: NSError?  
  3. // success is a Bool:  
  4. let success = someString.writeToURL(someURL,  
  5.                                     atomically: true,  
  6.                                     encoding: NSUTF8StringEncoding,  
  7.                                     error: &error)  
  8. if !success {  
  9.     // Log information about the error:  
  10.     println("Error writing to URL: \(error!)")  
  11. }  

 

程式員所導致的錯誤可以用Swift標準庫(Swift Standard Library)函數fatalError("Error message”)來標記,將其在控制台記錄為錯誤訊息並無條件中止執行。還可以使用assert(), assertionFailure(), precondition()和preconditionFailure()這些函數。

Swift第一次發布時,一些非蘋果平台開發人員已經準備好了火把和乾草叉。他們聲稱Swift不能算是“真正的語言”,因為它缺乏異常處理。但是,Cocoa和Cocoa Touch社區對此不予理睬,我們知道NSError和NSException那個時候就存在了。就我個人而言,我相信蘋果公司仍然在思考實現錯誤和異常處理的正確方式。我還認為直到問題解決了,蘋果公司才會公開Swift源碼。這一切問題在Swift 2.0中全被掃清了。

Swift 2.0中的錯誤處理

在Swift 2.0中,如果想要拋出錯誤,那麼拋出的對象必須符合ErrorType協議。可能正如你所願,NSError就符合該協議。枚舉在這裡用來給錯誤進行分類。

 

[cpp] view plaincopy 
  1. enum AwfulError: ErrorType {  
  2.     case Bad  
  3.     case Worse  
  4.     case Terrible  
  5. }  

 

然後如果一個可能拋出一個或多個錯誤的函數或方法會被throws關鍵字標記:

 

[cpp] view plaincopy 
  1. func doDangerousStuff() throws -> SomeObject {  
  2.     // If something bad happens throw the error:  
  3.     throw AwfulError.Bad  
  4.   
  5.     // If something worse happens, throw another error:   
  6.     throw AwfulError.Worse  
  7.   
  8.     // If something terrible happens, you know what to do:   
  9.     throw AwfulError.Terrible  
  10.   
  11.     // If you made it here, you can return:  
  12.     return SomeObject()  
  13. }  

 

為了捕獲錯誤,新型的do-catch語句出現了:

 

[cpp] view plaincopy 
  1. do {  
  2.     let theResult = try obj.doDangerousStuff()  
  3. }  
  4. catch AwfulError.Bad {  
  5.     // Deal with badness.  
  6. }  
  7. catch AwfulError.Worse {  
  8.     // Deal with worseness.  
  9. }  
  10. catch AwfulError.Terrible {  
  11.     // Deal with terribleness.  
  12. }  
  13. catch ErrorType {  
  14.     // Unexpected error!  
  15. }  

 

這個do-catch語句和switch語句有一些相似之處,被捕獲的錯誤詳盡無遺,因此你可以使用這種樣式來捕獲拋出的錯誤。還要注意關鍵字try的使用。它是為了明確地標示拋出的程式碼,因此當閱讀代碼的時候,你能夠立刻找到錯誤在哪裡。

關鍵字try的變體是“try!”。這個關鍵字大概也適用於那些程式員導致的錯誤。如果使用“try!”標記一個被調用的拋出對象中的方法,你等於告訴編譯器這個錯誤永遠不會發生,並且你也不需要捕獲它。如果該語句本身產生了錯誤(error),應用程式會停止執行,那麼你就要開始調試了。

 

[cpp] view plaincopy 
  1. let theResult = try! obj.doDangerousStuff()  

 

與Cocoa和Cocoa Touch架構間的互動

現在的問題是,你如何在Swift 2.0中處理爺爺級的NSError API呢?蘋果公司已經在Swift 2.0中為統一程式碼為作了大量工作,並且已經為未來寫入Swift的架構準備方法。Cocoa和Cocoa Touch中可以產生NSError執行個體的方法和函數有蘋果公司的簽名( signature),可以自動轉換為Swift新的錯誤處理方式。

例如,這個NSString的構造器( initializer)在Swift 1.x中就有以下籤名:

 

[cpp] view plaincopy 
  1. convenience init?(contentsOfFile path: String,  
  2.                   encoding enc: UInt,  
  3.                   error error: NSErrorPointer)  

 

Swift 2.0中,簽名被轉換成:

 

[js] view plaincopy 
  1. convenience init(contentsOfFile path: String,  
  2.                  encoding enc: UInt) throws  

 

注意:在Swift 2.0中,構造器不再被標記為failable,它並不需要NSErrorPointer來做參數,而是使用拋出異常的方式顯式地指示潛在的失敗。

下面的例子使用了這種新的簽名:

 

[cpp] view plaincopy 
  1. do {  
  2.     let str = try NSString(contentsOfFile: "Foo.bar",  
  3.                            encoding: NSUTF8StringEncoding)  
  4. }  
  5. catch let error as NSError {  
  6.     print(error.localizedDescription)  
  7. }  

 

注意錯誤是如何被捕獲的,並且如何被轉換成了一個NSError執行個體,這樣你就可以擷取與其相似API的資訊了。事實上,任何ErrorType類型的執行個體都可以轉換成NSError類型。

最後說說@finally

細心的讀者可能已經注意到,Swift 2.0引入了一個新的do-catch語句,而不是do-catch-finally。不管是否捕捉到錯誤的情況下,你如何指定必須啟動並執行代碼呢?為此,現在可以使用defer語句,用來延遲代碼塊的執行直到當前的範圍結束。

 

[cpp] view plaincopy 
  1. // Some scope:  
  2. {  
  3.     // Get some resource.  
  4.   
  5.     defer {  
  6.         // Release resource.  
  7.     }  
  8.   
  9.     // Do things with the resource.  
  10.     // Possibly return early if an error occurs.  
  11.   
  12. } // Deferred code is executed at the end of the scope.  

 

Swift 2.0將Cocoa和Cocoa Touch的錯誤處理機制凝聚為具有現代風格的用法,這是一項偉大的工作,也會使許多程式員倍感親切。統一行為是不錯的定位,會使Swift語言和其所繼承的架構逐步發展。

(翻譯/白雲鵬 友情審校/汪洋)

文章來源:Big Nerd Ranch

作者簡介:Juan Pablo Claude,來自智利首都聖地亞哥,畢業於美國北卡羅萊納大學教堂山分校(University of North Carolina at Chapel Hill),獲化學博士學位,後入阿拉巴馬大學伯明翰分校(University of Alabama at Birmingham)任教。2005年底作為Cocoa和Django架構程式開發人員加入Big Nerd Ranch。此前,有過DOS環境下C編程,Windows環境下使用C++編寫資料分析應用程式的經曆。

譯者簡介:白雲鵬,行動裝置 App開發人員,個人部落格:http://baiyunpeng.com。

 

不算“真正的語言”?詳說Swift 2.0中的錯誤處理

相關文章

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.