swift错误和异常处理

异常 (exception) 和错误 (error)。javascript

在 Objective-C 开发中,异常每每是由程序员的错误致使的 app 没法继续运行,好比咱们向一个没法响应某个消息的NSObject 对象发送了这个消息,会获得 NSInvalidArgumentException 的异常,并告诉咱们 "unrecognized selector sent to instance";好比咱们使用一个超过数组元素数量的下标来试图访问 NSArray 的元素时,会获得NSRangeException 。相似因为这样所致使的程序没法运行的问题应该在开发阶段就被所有解决,而不该当出如今实际的产品中。相对来讲,由 NSError 表明的错误更多地是指那些“合理的”,在用户使用 app 中可能遇到的状况:好比登录时用户名密码验证不匹配,或者试图从某个文件中读取数据生成 NSData 对象时发生了问题 (好比文件被意外修改了) 等等。html

可是 NSError 的使用方式其实变相在鼓励开发者忽略错误。想想在使用一个带有错误指针的 API 时咱们作的事情吧。咱们会在 API 调用中产生和传递 NSError ,并藉此判断调用是否失败。做为某个可能产生错误的方法的使用者,咱们用传入 NSErrorPointer 指针的方式来存储错误信息,而后在调用完毕后去读取内容,并确认是否发生了错误。好比在 Objective-C 中,咱们会写相似这样的代码:java

NSError *error; BOOL success = [data writeToFile: path options: options error: &error]; if(error) { // 发生了错误 }

这很是棒,可是有一个问题:在绝大多数状况下,这个方法并不会发生什么错误,而不少工程师也为了省事和简单,会将输入的 error 设为 nil ,也就是不关心错误 (由于可能他们从没见过这个 API 返回错误,也不知要如何处理)。因而调用就变成了这样:程序员

[data writeToFile: path options: options error: nil];

可是事实上这个 API 调用是会出错的,好比设备的磁盘空间满了的时候,写入将会失败。可是当这个错误出现并让你的 app 陷入难堪境地的时候,你几乎无从下手进行调试 -- 由于系统曾经尝试过通知你出现了错误,可是你却选择视而不见。编程

在 Swift 2.0 中,Apple 为这么语言引入了异常机制。如今,这类带有 NSError 指针做为参数的 API 都被改成了能够抛出异常的形式。好比上面的 writeToFile:options:error: ,在 Swift 中变成了:swift

public func writeToFile(path: String, options writeOptionsMask: NSDataWritingOptions) throws

咱们在使用这个 API 的时候,再也不像以前那样传入一个 error 指针去等待方法填充,而是变为使用 try catch 语句:数组

do { try d.writeToFile("Hello", options: []) } catch let error as NSError { print ("Error: \(error.domain)") }

若是你不使用 try 的话,是没法调用 writeToFile: 方法的,它会产生一个编译错误,这让咱们没法有意无心地忽视掉这些错误。在上面的示例中 catch 将抛出的异常 (这里就是个 NSError ) 用 let 进行了类型转换,这其实主要是针对 Cocoa 现有的 API 的,是对历史的一种妥协。对于咱们新写的可抛出异常的 API,咱们应当抛出一个实现了ErrorType 的类型, enum 就很是合适,举个例子:xcode

enum LoginError: ErrorType { case UserNotFound, UserPasswordNotMatch } func login(user: String, password: String) throws { //users 是 [String: String],存储[用户名:密码] if !users.keys.contains(user) { throw LoginError.UserNotFound } if users[user] != password { throw LoginError.UserPasswordNotMatch } print("Login successfully.") }

这样的 ErrorType 能够很是明确地指出问题所在。在调用时, catch 语句实质上是在进行模式匹配:安全

do { try login("onevcat", password: "123") } catch LoginError.UserNotFound { print("UserNotFound") } catch LoginError.UserPasswordNotMatch { print("UserPasswordNotMatch") } // Do something with login user

若是你以前写过 Java 或者 C# 的话,会发现 Swift 中的 try catch 块和它们中的有些不一样。在那些语言里,咱们会把可能抛出异常的代码都放在一个 try 里,而 Swift 中则是将它们放在 do 中,并只在可能发生异常的语句前添加 try。相比于 Java 或者 C# 的方式,Swift 里咱们能够更清楚地知道是哪个调用可能抛出异常,而没必要逐句查阅文档。网络

固然,Swift 如今的异常机制也并非十全十美的。最大的问题是类型安全,不借助于文档的话,咱们如今是没法从代码中直接得知所抛出的异常的类型的。好比上面的 login 方法,光看方法定义咱们并不知道 LoginError 会被抛出。一个理想中的异常 API 可能应该是这样的:

func login(user: String, password: String) throws LoginError

很大程度上,这是因为要与之前的 NSError 兼容所致使的妥协,对于以前的使用 NSError 来表达错误的 API,咱们所获得的错误对象自己就是用像 domain 或者 error number 这样的属性来进行区分和定义的,这与 Swift 2.0 中的异常机制所抛出的直接使用类型来描述错误的思想暂时是没法兼容的。不过有理由相信随着 Swift 的迭代更新,这个问题会在不久的未来获得解决。

另外一个限制是对于非同步的 API 来讲,抛出异常是不可用的 -- 异常只是一个同步方法专用的处理机制。Cocoa 框架里对于异步 API 出错时,保留了原来的 NSError 机制,好比很经常使用的 NSURLSession 中的 dataTask API:

func dataTaskWithURL(_ url: NSURL, completionHandler completionHandler: ((NSData!, NSURLResponse!, NSError!) -> Void)?) -> NSURLSessionDataTask

对于异步 API,虽然不能使用异常机制,可是由于这类 API 通常涉及到网络或者耗时操做,它所产生错误的可能性要高得多,因此开发者们其实没法忽视这样的错误。可是像上面这样的 API 其实咱们在平常开发中每每并不会去直接使用,而会选择进行一些封装,以求更方便地调用和维护。一种如今比较经常使用的方式就是借助于 enum 。做为 Swift 的一个重要特性,枚举 (enum) 类型如今是能够与其余的实例进行绑定的,咱们还可让方法返回枚举类型,而后在枚举中定义成功和错误的状态,并分别将合适的对象与枚举值进行关联:

enum Result { case Success(String) case Error(NSError) } func doSomethingParam(param:AnyObject) -> Result { //...作某些操做,成功结果放在 success 中 if success { return Result.Success("成功完成") } else { let error = NSError(domain: "errorDomain", code: 1, userInfo: nil) return Result.Error(error) } }

在使用时,利用 switch 中的 let 来从枚举值中将结果取出便可:

let result = doSomethingParam(path) switch result { case let .Success(ok): let serverResponse = ok case let .Error(error): let serverResponse = error.description }

在 Swift 2.0 中,咱们甚至能够在 enum 中指定泛型,这样就使结果统一化了。

enum Result<T> { case Success(T) case Failure(NSError) }

咱们只须要在返回结果时指明 的类型,就可使用一样的 Result 枚举来表明不一样的返回结果了。这么作能够减小代码复杂度和可能的状态,同时不是优雅地解决了类型安全的问题,可谓一箭双雕。

所以,在 Swift 2 时代中的错误处理,如今通常的最佳实践是对于同步 API 使用异常机制,对于异步 API 使用泛型枚举。

推荐阅读:
相关文章
相关标签/搜索