做者:Olivier Halligon,原文连接,原文日期:2015-12-17
译者:JackAlan;校对:靛青K;定稿:Channehtml
今天的文章讲解如何在 Swift 中进行错误处理。git
说实话,为了配合这个冬季❄️☃️,我取了一个有趣的文章标题。github
译者注:原文标题为 Let it throw, Let it throw! 是模仿冰雪奇缘的主题曲 Let it go ,而且文章的副标题也在模仿冰雪奇缘的经典台词。swift
还记得 Objective-C 吗?那时1,官方的方法是经过传入一个 NSError*
的引用进行错误处理。api
Objective-C NSError* error; BOOL ok = [string writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error]; if (!ok) { NSLog(@“发生了一个错误: %@", error); }
那简直是太痛苦了。以致于许多人不想甚至是懒得去检查错误,只是简单的在那里传一个 NULL
。这是很不负责且不安全的行为。安全
Swift 2.0 之后,苹果决定采用一种不一样的方式进行错误处理:使用 throw
2。框架
使用 throw
很是的简单:dom
若是你想建立一个可能出错的函数,用 throws 标记在它的签名处;函数
若是须要的话,能够在函数中使用 throw someError
;atom
在调用的地方,你必须明确的在能抛出错误3的方法的前面使用 try
;
可使用 do { … } catch { … }
这样的结构用来捕获并处理错误。
看起来像这样:
// 定义一个能够抛错误的方法… func someFunctionWhichCanFail(param: Int) throws -> String { ... if (param > 0) { return "somestring" } else { throw NSError(domain: "MyDomain", code: 500, userInfo: nil) } } // … 而后调用这个方法 do { let result: String = try someFunctionWhichCanFail(-2) print("success! \(result)") } catch { print("Oops: \(error)") }
你能够看到 someFunctionWitchCanFail
返回了一个普通的 String
,当一切正常的状况下, String
也是其返回值的类型。先考虑最简单的状况(在 do { … }
中的),“一般状况下”能够很方便的调用这个函数去处理没有错误发生的状况。
惟一的这些方法可能会出错的提醒就是try
关键字,编译器强制让你把 try
添加到方法调用的位置的前面,不然就像是调用一个无抛出错误的方法。而后,只须要在一个单独的地方(在 catch
里)写错误处理的代码。
要注意的是你能够在 do
代码段中写多于一行的代码(而且 try
能够调用不止一个抛错误的方法)。若是一切顺利的话,将会像预期的那样执行那些方法,可是一旦方法出错就会跳出 do
代码段,进入 catch
处。对于那些有不少潜在错误的大段代码来讲,你能够在一个单一的错误路径中处理全部的错误,这也是很是方便的。
OK,在这个例子下,咱们仍然得用 NSError
处理错误,这有点痛苦。用 ==
来比较域和错误代码,以及制做一个域和常量代码的列表,只是为了知道咱们获得了什么错误以及如何正确的处理。。。哎哟。
可是咱们能够解决这个问题!若是用Enums as Constants这篇文章里的知识:用 enum
替代 errors,将会怎样?
好吧,有一个好消息,那就是苹果提供了新的错误处理模式。事实上,当一个函数抛出错误时,它能够抛出任何听从 ErrorType
的错误。 NSError
是其中的类型之一,可是你也能够本身搞一个,苹果也推荐这么作。
最适合 ErrorType
类型的就是 enum
了,若是有须要的话,甚至两者之间能够有关联值。好比:
enum KristoffError : ErrorType { case ClumsyWayHeWalks case GrumpyWayHeTalks case PearShapedSquareShapedWeirdnessOfHisFeet case NotWashedSince(days: Int) }
如今你就能够在一个函数里使用 throw KristoffError.NotWashedSince(days: 3)
来抛出错误,而后在调用的地方使用 catch KristoffError.NotWashedSince(let days)
来处理这些错误:
func loveKristoff() throws -> Void { guard daysSinceLastShower == 0 else { throw KristoffError.NotWashedSince(days: daysSinceLastShower) } ... } do { try loveKristoff() } catch KristoffError.NotWashedSince(let days) { print("Ewww, he hasn't had a shower since \(days) days!") } catch { // 全部其余类型的错误 print("I prefer we stay friends") }
相比此前,这种方式更容易的捕获错误!
这也让错误拥有了清晰的名字、常量以及关联值。再也没有复杂的 userInfo
了,在 enum 中你能够清楚地看到值的关联,就像如上例子中的 days
,而且它只对特定的类型有效(不会对 ClumsyWayHeWalks
中的 days
关联值有效)。
当你调用一个正在抛出错误的函数时,抛出的错误就会被调用函数中的 do...catch
捕获。可是若是错误没有被捕获,它就会被传递到上一层。好比:
func doFail() throws -> Void { throw … } func test() { do { try doTheActualCall() } catch { print("Oops") } } func doTheActualCall() throws { try doFail() }
这里,当 doFail
被调用时,潜在的错误没有被 doTheActualCall
捕获(没有 do...catch
来捕获它),因此它就被传递到 test()
函数。因为 doTheActualCall
没有捕获任何错误,因此它必须被标记为 throws
:即便它不能经过本身抛出错误,但仍能传递。它本身不能处理错误,必须抛出到更高层。
另外一方面,test()
在内部捕获全部的错误,因此,即便它调用一个抛出函数(try doTheActualCall()
),这个函数抛出的全部的错误都会在 do...catch
块中被捕获。函数 test()
自己不抛出错误,因此调用者也不要知道其内部行为。
你如今可能很好奇,如何知道方法到底抛出哪一种错误。的确,被 throws
标记的函数到底能抛出哪一种 ErrorType
?它能抛出 KristoffErrors
、JSONErrors
或者其余类型吗?我到底须要捕获哪一种呢?
好吧,这的确是个问题。目前,因为一些二进制接口以及弹性问题(resilience concerns)4,这仍是不可能的。惟一的方式就是用你代码的文档。
但这也是一件好事。好比说,假如你用了两个库,MyLibA
中函数 funcA
会抛出 MyLibAError
错误,MyLibB
中函数 funcB
会抛出 MyLibBError
错误。
而后你可能想建立你本身的库 MyLibC
,封装以前的两个库,用函数 funcC()
调用 MyLibA.funcA()
和 MyLibB.funcB()
。因此,函数 funcC
的结果可能会抛出 MyLibAError
或者 MyLibBError
。并且,若是你添加了另外一个抽象层,这就变得很糟糕了,会有更多的错误类型被抛出。若是我不得不把它们都列出来,而且调用的地方须要把它们所有捕获,这将会形成一堆冗长的签名和 catch
代码。
基于上面的缘由,也为了防止你的内部错误超出你的库的做用域,以及为了限制那些必须由用户处理的错误类型的数量,我建议把错误类型的做用域限制在每一个抽象层次。
在如上的例子中,你应该抛出 MyLibCErrors
取而代之,而不是让 funcC
直接传递 MyLibAErrors
和 MyLibBErrors
。个人建议有以下的两个缘由,都是和抽象相关的:
你的用户不该该须要知道你在内部使用哪一个库。若是未来的某天,你决定改变你的实现:使用 SomeOtherPopularLibA
替代MyLibA
,显然这个库不会抛出相同的错误,你本身的 MyLibC
框架的调用者不须要知道或关心。这就是抽象应该干的事。
调用者不该该须要处理全部的错误。固然你能够捕获那些错误中的一些而且在内部处理:把 MyLibA
抛出的全部错误都暴露给用户是没有意义的,好比一个 FrameworkConfigurationError
错误代表你误用了 MyLibA
框架而且忘了调用它的 setup()
方法,或者是任何不该该由用户作的事情,由于用户根本无能为力。这种错误是你的错误,而不是别人的。
因此,取而代之,你的 funcC
应该极可能捕获全部 MyLibAErrors
和 MyLibBErrors
,封装它们为 MyLibCErrors
替代。这样的话,你的框架的使用者不须要知道你在内部使用了什么。你能够在任什么时候候改变你的内部实现和使用的库,而且你只须要给用户暴露那些他们可能须要关注的错误。
译者注:原标题为
We finish each others sandwiches
,是在模仿冰雪奇缘中王子和公主的对话,表示和其余博主以及读者的一种亲近的关系。
throw
话题和 Swift 2.0 的错误处理模型还有不少东西可讲,我本能够讲一些关于 try?
和 try!
,或者关于高阶函数中的 rethrows
关键字。
这里没有时间对每一个话题面面俱到了,那会使得个人文章很是长。可是别人有趣的文章将会帮你探索 Swift 错误处理的世界,包括但不限于:
Throw that don’t throw and Re…throws? by Rob Napier
Error Handling by Little Bites of Cocoa
What we learned from rewriting our robotic control software in Swift, by Brad Larson
... (别犹豫了,快去评论区分享更多连接吧!)
更多关于在 Objective-C 中错误处理的信息,能够参考这篇文章:NSError。今天的文章是关于 Swift 中的新方式的,因此别在旧事物上花费太多的时间。
尽管它叫 throw ,可是 throw
不是像 Java 或者 C++ 甚至 OC 中的 throw exception。可是使用的方式很是类似,苹果决定保留相同的措辞,因此习惯于 exceptions 的人会感到很是天然。
Swift 2.0 还不支持 typed throws,可是这里有一个关于添加这个特性的讨论,Chris Lattner 解释了 Swift 2 不支持的缘由,以及为何咱们须要 Swift 3.0 的弹性模型以得到这个特性。
好了,我保证这是我最后一次可耻使用 Frozen(《冰雪奇缘》) 标题了。
本文由 SwiftGG 翻译组翻译,已经得到做者翻译受权,最新文章请访问 http://swift.gg。