全面解析Swift 2錯(cuò)誤處理技術(shù)
譯文自從Swift 1開始就提供了錯(cuò)誤處理技術(shù)支持;這種技術(shù)是受來自于Objective-C的啟發(fā)而開發(fā)出的。Swift 2在這方面的改進(jìn)使得處理您的應(yīng)用程序中的意外狀態(tài)和條件更加簡(jiǎn)單直接。
就像其他普通的編程語言一樣,Swift中的錯(cuò)誤處理技術(shù)也存在不同類型,具體情況則要取決于遇到的錯(cuò)誤類型和您的應(yīng)用程序的總體結(jié)構(gòu)。
本教程將帶您通過具體的例子來介紹如何最有效地處理常見的錯(cuò)誤情況。你還會(huì)看到如何升級(jí)早期版本Swift編寫的項(xiàng)目中的錯(cuò)誤處理模塊。文章***,將集中介紹未來版本的Swift可能提供的錯(cuò)誤處理技術(shù)!
【注意】本教程假定您已熟悉使用Swift 2語法,特別是枚舉(enumberations)和可選值(optionals)。如果你需要復(fù)習(xí)一下這些概念,建議您閱讀Greg Heo的文章“What’s New in Swift 2”。
接下來,就讓我們開始踏上Swift 2錯(cuò)誤處理技術(shù)的學(xué)習(xí)征程,你一定會(huì)體會(huì)到不一樣的樂趣的!
簡(jiǎn)介
本教程提供了兩個(gè)初學(xué)者案例供大家學(xué)習(xí)使用,它們的下載地址分別是:https://cdn4.raywenderlich.com/wp-content/uploads/2016/04/Avoiding-Errors-with-nil-Starter.playground-1.zip和 https://cdn4.raywenderlich.com/wp-content/uploads/2016/04/Avoiding-Errors-with-Custom-Handling-Starter.playground-1.zip。
為了跟蹤本文中的實(shí)例代碼,請(qǐng)下載這兩個(gè)示例工程。
首先,請(qǐng)使用Xcode打開***個(gè)初學(xué)者案例Errors with nil。通讀工程中的代碼,你會(huì)看到有幾個(gè)類、結(jié)構(gòu)和枚舉定義。
請(qǐng)注意代碼的以下部分:
- protocol MagicalTutorialObject {
 - var avatar: String { get }
 - }
 
這個(gè)協(xié)議適用于本教程中使用的所有類和結(jié)構(gòu),它用于為工程中的每一個(gè)對(duì)象有關(guān)信息提供可視化描述——打印到控制臺(tái)中。
- enum MagicWords: String {
 - case Abracadbra = "abracadabra"
 - case Alakazam = "alakazam"
 - case HocusPocus = "hocus pocus"
 - case PrestoChango = "presto chango"
 - }
 
此枚舉定義了一些可用于創(chuàng)建一個(gè)魔法的咒語。
- struct Spell: MagicalTutorialObject {
 - var magicWords: MagicWords = .Abracadbra
 - var avatar = "*"
 - }
 
這是一個(gè)魔法的基本構(gòu)建塊。默認(rèn)情況下,它的初始化咒語值為“Abracadbra”。
現(xiàn)在,您已經(jīng)熟悉了我們要介紹的例子中的一些基本術(shù)語,那么接下來您要開始施行一些“魔法”了。
為什么要關(guān)注錯(cuò)誤處理
錯(cuò)誤處理是一種以優(yōu)雅方式失敗的藝術(shù)。
——出自《Swift Apprentice》一書的第12章(“錯(cuò)誤處理”)
好的錯(cuò)誤處理方法有助于增強(qiáng)最終用戶和軟件維護(hù)人員的體驗(yàn),使其更容易地查明問題、 問題原因及其可能產(chǎn)生的嚴(yán)重后果。代碼中錯(cuò)誤處理得越具體,問題就越容易診斷。錯(cuò)誤處理也可以讓系統(tǒng)用適當(dāng)?shù)姆绞綊伋鲥e(cuò)誤,從而不至于挫敗或擾亂用戶。
但并不是需要處理一切錯(cuò)誤。如果程序員不處理,語言功能本身也可能會(huì)使您完全避免某些類別的錯(cuò)誤。作為一般規(guī)則,如果你能避免錯(cuò)誤的可能性,那么請(qǐng)遵循這樣的設(shè)計(jì)思路。如果你不能避免潛在的錯(cuò)誤條件,那么顯式處理錯(cuò)誤是你***的選擇。
使用nil避免Swift錯(cuò)誤
由于Swift提供了優(yōu)雅的可選項(xiàng)(Optionals)處理能力;所以,在你期望值出現(xiàn)但未提供值的地方您完全可以避免錯(cuò)誤條件。作為一個(gè)聰明的程序員,你可以操縱此功能:通過一個(gè)錯(cuò)誤條件判斷故意返回nil。這種方法最適合用于當(dāng)你到達(dá)一個(gè)錯(cuò)誤狀態(tài)但你不必采取任何措施的情形;也就是說,你選擇不采取措施而不是采取緊急措施。
兩個(gè)典型的使用nil避免Swift錯(cuò)誤的例子是可失敗構(gòu)造器和guard語句。
可失敗構(gòu)造器(failable initializer)
可失敗構(gòu)造器能夠防止一個(gè)對(duì)象的創(chuàng)建——除非已提供了足夠的信息。在Swift 2以前(以及在其他語言中),這一功能通常是通過工廠方法模式實(shí)現(xiàn)的。
Swift使用這種模式的一個(gè)示例在createWithMagicWords方法中就會(huì)看到:
- static func createWithMagicWords(words: String) -> Spell? {
 - if let incantation = MagicWords(rawValue: words) {
 - var spell = Spell()
 - spell.magicWords = incantation
 - return spell
 - }
 - else {
 - return nil
 - }
 - }
 
上述初始化器試圖使用提供的咒語創(chuàng)建一個(gè)魔法(spell);但如果言語不是咒語(magic words),改以返回nil。
你可以在本教程***部的代碼中觀察魔法的創(chuàng)建來查看這種方法的使用:
你會(huì)注意到: first使用咒語“abracadabra”成功創(chuàng)建一個(gè)魔法,而咒語“ascendio”并不會(huì)產(chǎn)生這種效果,并返回second的值為nil。
工廠方法是一種老式的編程風(fēng)格。其實(shí),在Swift中有更好的方式來實(shí)現(xiàn)同樣的事情。為此,你只需使用一個(gè)可失敗構(gòu)造器而不是工廠方法來更新spell擴(kuò)展即可。
于是,你可以刪除createWithMagicWords(_:)方法并把它替換為以下內(nèi)容:
- init?(words: String) {
 - if let incantation = MagicWords(rawValue: words) {
 - self.magicWords = incantation
 - }
 - else {
 - return nil
 - }
 - }
 
在這里,你簡(jiǎn)化了代碼——并沒有顯式創(chuàng)建和返回spell對(duì)象。
給first和second賦值的語句行現(xiàn)在會(huì)拋出編譯時(shí)錯(cuò)誤:
- let first = Spell.createWithMagicWords("abracadabra")
 - let second = Spell.createWithMagicWords("ascendio")
 
你需要更改這些語句——使用新的初始化器。為此,你只需要使用以下內(nèi)容替換上面的代碼行即可:
- let first = Spell(words: "abracadabra")
 - let second = Spell(words: "ascendio")
 
此后,所有錯(cuò)誤應(yīng)修復(fù)完畢,再編譯示例工程應(yīng)沒有什么錯(cuò)誤。這樣修改以后,您的代碼整潔多了——但是其實(shí)你可以做得比這更好!
Guard語句
guard是一種斷言某事是否為真實(shí)的快速方法。然后,如果檢查失敗,您可以執(zhí)行事先設(shè)計(jì)的代碼塊。
Guard是Swift 2引入的,通常用于通過調(diào)用堆棧以冒泡法處理錯(cuò)誤,最終錯(cuò)誤將得到處理。Guard語句允許提前退出一個(gè)函數(shù)或方法;這使得程序員更清楚對(duì)于剩下的要運(yùn)行的處理邏輯需要存在哪些條件。
為了進(jìn)一步精簡(jiǎn)魔法的可失敗構(gòu)造器,我們?cè)賮硎褂胓uard方法修改一下上面代碼:
- init?(words: String) {
 - guard let incantation = MagicWords(rawValue: words) else {
 - return nil
 - }
 - self.magicWords = incantation
 - }
 
這樣修改后,就沒有必要再在一個(gè)單獨(dú)的行上使用一個(gè)單獨(dú)的else子句;而且,失敗的情況也更加明顯,因?yàn)樗F(xiàn)在位于初始化程序的頂部。
請(qǐng)注意,***和第二個(gè)魔法常量的值沒有改變,但代碼卻變得更為精簡(jiǎn)了。
使用定制處理器避免錯(cuò)誤
上面通過精簡(jiǎn)魔法的可失敗構(gòu)造器并通過巧妙地使用nil已經(jīng)可以避免一些錯(cuò)誤。接下來,讓我們來處理一些更復(fù)雜的錯(cuò)誤。
為了學(xué)習(xí)接下來的錯(cuò)誤處理技術(shù),請(qǐng)打開工程Avoiding-Errors-with-Custom-Handling-Starter.playground-1。
請(qǐng)注意下面代碼中的特征:
- struct Spell: MagicalTutorialObject {
 - var magicWords: MagicWords = .Abracadbra
 - var avatar = "*"
 - init?(words: String) {
 - guard let incantation = MagicWords(rawValue: words) else {
 - return nil
 - }
 - self.magicWords = incantation
 - }
 - init?(magicWords: MagicWords) {
 - self.magicWords = magicWords
 - }
 - }
 
這里定義的是Spell的構(gòu)造器,我們對(duì)之作了簡(jiǎn)要修改以匹配您在本教程的***部分所完成的工作。此外,請(qǐng)注意這里還使用了MagicalTutorialObject協(xié)議和另外一個(gè)為了方便使用而引入的可失敗構(gòu)造器。
- protocol Familiar: MagicalTutorialObject {
 - var noise: String { get }
 - var name: String? { get set }
 - init()
 - init(name: String?)
 - }
 
這里的Familiar協(xié)議將適用于各類寵物(如蝙蝠和蟾蜍,為女巫所豢養(yǎng)和驅(qū)使),在本文第二個(gè)示例工程中一直這樣使用。
接下來看女巫(Witch)的定義:
- struct Witch: MagicalBeing {
 - var avatar = "*"
 - var name: String?
 - var familiar: Familiar?
 - var spells: [Spell] = []
 - var hat: Hat?
 - init(name: String?, familiar: Familiar?) {
 - self.name = name
 - self.familiar = familiar
 - if let s = Spell(magicWords: .PrestoChango) {
 - self.spells = [s]
 - }
 - }
 - init(name: String?, familiar: Familiar?, hat: Hat?) {
 - self.init(name: name, familiar: familiar)
 - self.hat = hat
 - }
 - func turnFamiliarIntoToad() -> Toad {
 - if let hat = hat {
 - if hat.isMagical { // When have you ever seen a Witch perform a spell without her magical hat on ? :]
 - if let familiar = familiar { // Check if witch has a familiar
 - if let toad = familiar as? Toad { // Check if familiar is already a toad - no magic required
 - return toad
 - } else {
 - if hasSpellOfType(.PrestoChango) {
 - if let name = familiar.name {
 - return Toad(name: name)
 - }
 - }
 - }
 - }
 - }
 - }
 - return Toad(name: "New Toad") // This is an entirely new Toad.
 - }
 - func hasSpellOfType(type: MagicWords) -> Bool { // Check if witch currently has appropriate spell in their spellbook
 - return spells.contains { $0.magicWords == type }
 - }
 - }
 
現(xiàn)在,讓我們簡(jiǎn)單作一下總結(jié):
初始化女巫:使用name和familiar參數(shù),或者再添加一個(gè)hat參數(shù)。
一個(gè)女巫知道有限數(shù)量的魔法;這些存儲(chǔ)魔法在spells中,spells是一個(gè)魔法對(duì)象的數(shù)組。
每一個(gè)巫婆似乎都有一個(gè)嗜好,即在turnFamiliarIntoToad()方法中通過使用PrestoChango咒語把她的寵物變成一只癩蛤蟆。
請(qǐng)注意上面方法turnFamiliarIntoToad()中的縮進(jìn)字符的數(shù)量。此外,你還應(yīng)當(dāng)注意:該方法中如果有任何差錯(cuò),將返回一只全新的蟾蜍。這似乎令人費(fèi)解(而且有些錯(cuò)誤!)。在下一節(jié)中,通過自定義錯(cuò)誤處理技術(shù)您會(huì)完全明白這段代碼的。
使用重構(gòu)技術(shù)
在上面的turnFamiliarIntoToad()方法中使用了多級(jí)嵌套語句來控制程序流程,而閱讀這樣的嵌套代碼相當(dāng)費(fèi)勁。
如你前面看到的,Guard語句和多個(gè)可選綁定的使用有助于清除上面金字塔式復(fù)雜代碼。然而,利用do-catch機(jī)制,通過從錯(cuò)誤狀態(tài)處理中解耦控制流能夠徹底消除這一問題。
do-catch機(jī)制通常出現(xiàn)在以下關(guān)鍵字前后:
throws
do
catch
try
defer
ErrorType
若要查看這些關(guān)鍵字的實(shí)際使用,你要拋出多個(gè)自定義錯(cuò)誤。首先,你要定義你希望處理的語句,你可以通過一個(gè)枚舉來列出一切可能出錯(cuò)的內(nèi)容。
然后,將下面的代碼添加到你的示例工程(一個(gè)與游樂場(chǎng)內(nèi)容有關(guān)的程序)的女巫(Witch)定義的上方:
- enum ChangoSpellError: ErrorType {
 - case HatMissingOrNotMagical
 - case NoFamiliar
 - case FamiliarAlreadyAToad
 - case SpellFailed(reason: String)
 - case SpellNotKnownToWitch
 - }
 
請(qǐng)注意與ChangoSpellError有關(guān)的兩點(diǎn):
它符合ErrorType協(xié)議,這是在Swift語言中定義錯(cuò)誤時(shí)的必要條件。
在SpellFailed情形下,可以針對(duì)魔法失敗通過一個(gè)關(guān)聯(lián)值指定一個(gè)自定義原因。
接下來,把throws關(guān)鍵字添加到方法簽名中,以指示調(diào)用此方法時(shí)可能會(huì)發(fā)生錯(cuò)誤:
- func turnFamiliarIntoToad() throws -> Toad {
 
然后,在MagicalBeing協(xié)議上也作一下更新:
- protocol MagicalBeing: MagicalTutorialObject {
 - var name: String? { get set }
 - var spells: [Spell] { get set }
 - func turnFamiliarIntoToad() throws -> Toad
 - }
 
既然你已經(jīng)列出所有錯(cuò)誤狀態(tài),接下來你可以重構(gòu)turnFamiliarIntoToad()方法。
處理帽子相關(guān)錯(cuò)誤
首先,修改下面的語句以確保女巫戴著她最重要的帽子,把語句:
- if let hat = hat {
 
修改為:
- guard let hat = hat else {
 - throw ChangoSpellError.HatMissingOrNotMagical
 - }
 
【注意】不要忘記刪除方法底部的}符號(hào);否則工程將會(huì)出現(xiàn)編譯錯(cuò)誤!
下一行包含一個(gè)布爾值檢查,也是與帽子相關(guān)的:
- if hat.isMagical {
 
您可以選擇添加一個(gè)單獨(dú)的guard語句來執(zhí)行這項(xiàng)檢查;但是,把一組檢查統(tǒng)一放在一行代碼中更為清楚。因此,你可以更改***個(gè)的guard語句,像下面這樣:
- guard let hat = hat where hat.isMagical else {
 - throw ChangoSpellError.HatMissingOrNotMagical
 - }
 
現(xiàn)在,經(jīng)這樣一修改,也一并消除了if hat.isMagical {檢查部分。
在下一節(jié)中,你會(huì)繼續(xù)解除那種成金字塔形可怕的條件語句。
處理Familiar有關(guān)錯(cuò)誤
【譯者注】在本文中,我把“familiar”翻譯為“寵物”,即前面為女巫所豢養(yǎng)和驅(qū)駛的各種小動(dòng)物。
接下來,讓我們修改檢測(cè)是否女巫是否含有familiar的語句,即把語句:
- if let familiar = familiar {
 
修改為從另一個(gè)guard語句中拋出一個(gè)錯(cuò)誤:
- guard let familiar = familiar else {
 - throw ChangoSpellError.NoFamiliar
 - }
 
目前,我們先忽略發(fā)生的任何錯(cuò)誤,因?yàn)槟憬酉聛淼拇a更改會(huì)使它們消失。
處理Toad相關(guān)錯(cuò)誤
在下一行中,如果巫婆想要對(duì)毫無戒心的兩棲類施加turnFamiliarIntoToad()咒語的話,你需要返回現(xiàn)有的蟾蜍,但使用一種明確的錯(cuò)誤會(huì)更好地告知她犯的錯(cuò)誤。為此,把以下內(nèi)容:
- if let toad = familiar as? Toad {
 - return toad
 - }
 
修改成如下語句:
- if familiar is Toad {
 - throw ChangoSpellError.FamiliarAlreadyAToad
 - }
 
注意到,這里把a(bǔ)s?修改為is,從而可以更簡(jiǎn)潔地檢查與協(xié)議的一致性,而不一定需要使用結(jié)果。關(guān)鍵字is還可以用于更普遍形式的類型比較。如果你有興趣更多地學(xué)習(xí)is和as,建議你閱讀蘋果官網(wǎng)中《Swift編程語言》的類型轉(zhuǎn)換部分(https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html)。
使用上面技術(shù),我們就可以把else子句里面的內(nèi)容放到else子句外面,從而刪除掉else。
處理魔法相關(guān)錯(cuò)誤
***,對(duì)hasSpellOfType(type:)方法的調(diào)用可以確保女巫在她的魔法書中存在適當(dāng)?shù)哪Х?。為此,我們把下面的代碼:
- if hasSpellOfType(.PrestoChango) {
 - if let toad = f as? Toad {
 - return toad
 - }
 - }
 
更改為如下代碼:
- guard hasSpellOfType(.PrestoChango) else {
 - throw ChangoSpellError.SpellNotKnownToWitch
 - }
 - guard let name = familiar.name else {
 - let reason = "Familiar doesn’t have a name."
 - throw ChangoSpellError.SpellFailed(reason: reason)
 - }
 - return Toad(name: name)
 
現(xiàn)在,您可以刪除***一行代碼,這是沒有問題的,即刪除下面一行:
- return Toad(name: "New Toad")
 
現(xiàn)在,你擁有了以下簡(jiǎn)練的方法。其中,我提供了幾點(diǎn)補(bǔ)充,以進(jìn)一步解釋代碼的作用:
- func turnFamiliarIntoToad() throws -> Toad {
 - // When have you ever seen a Witch perform a spell without her magical hat on ? :]
 - guard let hat = hat where hat.isMagical else {
 - throw ChangoSpellError.HatMissingOrNotMagical
 - }
 - // Check if witch has a familiar
 - guard let familiar = familiar else {
 - throw ChangoSpellError.NoFamiliar
 - }
 - // Check if familiar is already a toad - if so, why are you casting the spell?
 - if familiar is Toad {
 - throw ChangoSpellError.FamiliarAlreadyAToad
 - }
 - guard hasSpellOfType(.PrestoChango) else {
 - throw ChangoSpellError.SpellNotKnownToWitch
 - }
 - // Check if the familiar has a name
 - guard let name = familiar.name else {
 - let reason = "Familiar doesn’t have a name."
 - throw ChangoSpellError.SpellFailed(reason: reason)
 - }
 - // It all checks out! Return a toad with the same name as the witch's familiar
 - return Toad(name: name)
 - }
 
以前,從turnFamiliarIntoToad()方法中返回一個(gè)可選項(xiàng)僅表明了“施加這個(gè)魔法時(shí)出現(xiàn)了某種錯(cuò)誤”。但,像這樣使用自定義的錯(cuò)誤,你可以更清楚地表示錯(cuò)誤狀態(tài),從而對(duì)其做出相應(yīng)的反應(yīng)。
其他適合定制錯(cuò)誤的地方
現(xiàn)在,既然建立了方法可以拋出自定義Swift錯(cuò)誤,你就需要進(jìn)一步處理這些錯(cuò)誤。這樣做的標(biāo)準(zhǔn)機(jī)制稱為do-catch語句,這類似于在其他如Java語言中使用的try-catch機(jī)制。
現(xiàn)在,請(qǐng)將下面的代碼添加到你的工程文件的***:
- func exampleOne() {
 - print("") // Add an empty line in the debug area
 - // 1
 - let salem = Cat(name: "Salem Saberhagen")
 - salem.speak()
 - // 2
 - let witchOne = Witch(name: "Sabrina", familiar: salem)
 - do {
 - // 3
 - try witchOne.turnFamiliarIntoToad()
 - }
 - // 4
 - catch let error as ChangoSpellError {
 - handleSpellError(error)
 - }
 - // 5
 - catch {
 - print("Something went wrong, are you feeling OK?")
 - }
 - }
 
以下是該函數(shù)所實(shí)現(xiàn)的任務(wù):
1. 創(chuàng)建這個(gè)女巫的寵物,它是一只叫Salem的貓。
2. 創(chuàng)建女巫,名字叫Sabrina。
3. 嘗試把這只貓變成一只癩蛤蟆。
4. 捕獲一個(gè)ChangoSpellError錯(cuò)誤,并適當(dāng)?shù)靥幚礤e(cuò)誤。
5. ***,捕捉所有其他錯(cuò)誤并打印出一條友好的信息。
添加上述內(nèi)容后,你會(huì)看到一個(gè)編譯器錯(cuò)誤?,F(xiàn)在,我們著手解決這個(gè)問題。
handleSpellError()方法尚未定義,因此,把下列代碼添加到前面exampleOne()函數(shù)定義的上面:
- func handleSpellError(error: ChangoSpellError) {
 - let prefix = "Spell Failed."
 - switch error {
 - case .HatMissingOrNotMagical:
 - print("\(prefix) Did you forget your hat, or does it need its batteries charged?")
 - case .FamiliarAlreadyAToad:
 - print("\(prefix) Why are you trying to change a Toad into a Toad?")
 - default:
 - print(prefix)
 - }
 - }
 
***,把以下內(nèi)容添加到文件的底部并運(yùn)行工程代碼:
- exampleOne()
 
你會(huì)看到調(diào)試控制臺(tái)輸出顯示有關(guān)內(nèi)容:
捕獲錯(cuò)誤
下面給出在上面的代碼片段中使用的每一個(gè)Swift 2錯(cuò)誤處理技術(shù)的簡(jiǎn)短概括。
catch子句
你可以在Swift中使用模式匹配來處理特定錯(cuò)誤或把幾種錯(cuò)誤類型放在一起處理。
上面的代碼中向你演示了捕獲錯(cuò)誤的幾種用法:一個(gè)是捕獲特定的ChangoSpell錯(cuò)誤,一個(gè)是一起處理剩余錯(cuò)誤的情況。
try子句
你可以使用try子句并結(jié)合do-catch子句來清楚表明哪些行或代碼段可能拋出錯(cuò)誤。
你可以通過幾種不同的方式來使用try命令,上面使用過的是下面之一:
try——清楚和直接的do-catch語句中的標(biāo)準(zhǔn)用法,這是上面代碼中使用的方式。
try?——本質(zhì)上是通過忽視錯(cuò)誤的方式來處理錯(cuò)誤;如果拋出一個(gè)錯(cuò)誤,語句的結(jié)果將為nil。
try!——該子句強(qiáng)調(diào)一種期望結(jié)果:理論上,一個(gè)語句能夠拋出一個(gè)錯(cuò)誤;但實(shí)際上,這種錯(cuò)誤條件永遠(yuǎn)不會(huì)發(fā)生。try!子句可用于像加載文件這樣的編程代碼中,這種情況下你有把握確保某些所需的媒體存在。應(yīng)小心使用這個(gè)子句。
現(xiàn)在,讓我們具體地了解try?子句的用法。你可以把下列代碼剪切并粘貼到你上面文件的底部:
- func exampleTwo() {
 - print("") // Add an empty line in the debug area
 - let toad = Toad(name: "Mr. Toad")
 - toad.speak()
 - let hat = Hat()
 - let witchTwo = Witch(name: "Elphaba", familiar: toad, hat: hat)
 - let newToad = try? witchTwo.turnFamiliarIntoToad()
 - if newToad != nil { // Same logic as: if let _ = newToad
 - print("Successfully changed familiar into toad.")
 - }
 - else {
 - print("Spell failed.")
 - }
 - }
 
請(qǐng)注意上面代碼中exampleOne的不同之處。在這里,你不必關(guān)心特定錯(cuò)誤的輸出問題,但仍然要捕捉發(fā)生錯(cuò)誤的事實(shí)。這里并沒有創(chuàng)建蟾蜍寵物;所以,newToad的值為nil。
傳播錯(cuò)誤
throws
如果一個(gè)函數(shù)或方法拋出錯(cuò)誤,在Swift中需要使用throws關(guān)鍵字。拋出的錯(cuò)誤會(huì)沿調(diào)用堆棧向上自動(dòng)傳播,但人們普遍認(rèn)為讓錯(cuò)誤從其發(fā)生源地傳播太遠(yuǎn)是一個(gè)不好的做法。在整個(gè)代碼庫(kù)中增加錯(cuò)誤可能性的重大傳播可能會(huì)躲過恰當(dāng)?shù)腻e(cuò)誤處理機(jī)會(huì);為此,借助于throws關(guān)鍵字可以確保傳播能夠在代碼中記錄在案,并且對(duì)編程人員也很容易了解這一點(diǎn)。
rethrows
目前為止,你所看到的所有示例都使用了throws,但怎么使用rethrows呢?
rethrows告訴編譯器僅當(dāng)函數(shù)參數(shù)拋出錯(cuò)誤時(shí)這個(gè)函數(shù)才拋出錯(cuò)誤。下面是一個(gè)最直接的例子(無需將它添加到前面的文件中):
- func doSomethingMagical(magicalOperation: () throws -> MagicalResult) rethrows -> MagicalResult {
 - return try magicalOperation()
 - }
 
在這里,doSomethingMagical(_:)方法僅當(dāng)提供給函數(shù)的magicalOperation參數(shù)拋出錯(cuò)誤時(shí)才拋出錯(cuò)誤。如果成功了,它返回一個(gè)MagicalResult值。
操縱錯(cuò)誤處理行為
defer
雖然自動(dòng)傳播在大多數(shù)情況下工作良好,但也有些情況下,當(dāng)錯(cuò)誤在調(diào)用堆棧中向上傳播時(shí)你可能要進(jìn)一步控制你的應(yīng)用程序的行為。
Defer語句提供了一種機(jī)制,每當(dāng)退出當(dāng)前范圍允許程序執(zhí)行“清理”工作,如方法或函數(shù)返回時(shí)。它用于管理需要清理的資源——無論動(dòng)作是否成功。因此,在錯(cuò)誤處理上下文中尤為有用。
【譯者注】建議你結(jié)合C++/Java等語言中的finally子句加以理解。
為了了解defer的使用,請(qǐng)將下面的方法添加到Witch結(jié)構(gòu)的***:
- func speak() {
 - defer {
 - print("*cackles*")
 - }
 - print("Hello my pretties.")
 - }
 
然后,將下面的代碼添加到前面文件的底部:
- func exampleThree() {
 - print("") // Add an empty line in the debug area
 - let witchThree = Witch(name: "Hermione", familiar: nil, hat: nil)
 - witchThree.speak()
 - }
 - exampleThree()
 
在調(diào)試控制臺(tái)中,您應(yīng)該看到女巫在說完所有話后發(fā)出咯咯的笑聲。
有趣的是,defer語句是以其編程時(shí)順序的相反的順序執(zhí)行的。
現(xiàn)在,我們把另一個(gè)defer添加到speak()語句,以便女巫可以咯咯地發(fā)笑。然后,女巫在說完所有話后發(fā)出咯咯的笑聲:
- func speak() {
 - defer {
 - print("*cackles*")
 - }
 - defer {
 - print("*screeches*")
 - }
 - print("Hello my pretties.")
 - }
 
你注意到調(diào)試控制臺(tái)中的輸出順序了嗎?這正是defer語句的能力!
與錯(cuò)誤有關(guān)的更有趣的事情
本文中提供的上述Swift語句使其與很多其他受歡迎的語言保持了一致,從而把Swift從Objective-C基于NSError基礎(chǔ)的錯(cuò)誤處理方法中分離出來。而大多數(shù)情況下的Objective-C錯(cuò)誤都是直譯式的,編譯器中的靜態(tài)分析器能夠很好地幫助你確定你需要哪些錯(cuò)誤及何時(shí)需要捕獲錯(cuò)誤。
雖然do-catch相關(guān)支持語句在其他語言中也有很大的開銷;但是,在Swift語言中,它們基本上像任何其他語句一樣處理。這將確保它們的有效性和高效率。
但是,不要因?yàn)槟憧梢詣?chuàng)建自定義錯(cuò)誤并拋出錯(cuò)誤并隨意地使用。這方面,建議你針對(duì)你開發(fā)的工程先制定一些準(zhǔn)則:何時(shí)需要拋出和捕獲錯(cuò)誤。對(duì)此,我提出下列建議:
確保錯(cuò)誤類型在您的整個(gè)代碼庫(kù)中被清楚地命名。
當(dāng)單個(gè)錯(cuò)誤狀態(tài)時(shí)盡量使用可選值(Optionals)。
當(dāng)存在超過一個(gè)錯(cuò)誤狀態(tài)時(shí)使用自定義錯(cuò)誤處理技術(shù)。
不允許錯(cuò)誤從其源地傳播得太遠(yuǎn)。
未來的Swift錯(cuò)誤處理
在各種Swift論壇中經(jīng)常討論幾種高級(jí)錯(cuò)誤處理想法。其中,談?wù)撟疃嗟母拍钪皇欠穷愋突瘋鞑サ膯栴}。
“......我們相信我們可以擴(kuò)展我們當(dāng)前的模型以支持非類型化傳播的普遍錯(cuò)誤。這項(xiàng)工作怎樣才能做得很好——特別是在不完全犧牲代碼大小和性能的情況下,將引發(fā)大量深度研究??梢灶A(yù)測(cè),在Swift 2.0中實(shí)現(xiàn)這一方法是沒有問題的。”(來自《Swift 2.x Error Handling》)
不論你是否在享用Swift 3中的主流錯(cuò)誤處理思想,是否對(duì)于今天存在的東西滿意,令人高興的是,隨著語言技術(shù)的繼續(xù)發(fā)展,整潔的錯(cuò)誤處理技術(shù)正在各地積極討論中并不斷改進(jìn)。
小結(jié)
您可以下載本教程已完成的游樂場(chǎng)示例工程進(jìn)一步研究討論,地址是https://cdn2.raywenderlich.com/wp-content/uploads/2016/04/Magical-Error-Handling-in-Swift.zip。
如果你渴望看到有關(guān)Swift 3的新進(jìn)展,我推薦你參閱《Swift Language Proposals》(https://github.com/apple/swift/tree/master/docs/proposals)。
希望到目前為止,你已經(jīng)真正沉迷于Swift中的錯(cuò)誤處理技術(shù)。

















 
 
 







 
 
 
 