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

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

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

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 中变成了:

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 就非常合适,举个例子:

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 使用泛型枚举。

推荐阅读:

swift错误和异常处理的更多相关文章

  1. Swift基础--Swift中的异常处理

    Swift中的异常处理 OC中的异常处理:方法的参数要求传入一个error指针地址,方法执行完后,如果有错误,内部会给error赋值 Swift中的异常处理:有throws的方法,就要try起来,然后 ...

  2. PHP错误以及异常处理

    以前一直觉得php的异常处理没有什么,现在才发现这个还真是门学问,于是狠下心来好好研究了一下,写一篇文章,也作备忘吧. 1. php错误 无论是什么语言编程,都会有如下三种错误,当然php也不例外. ...

  3. Yii中的错误及异常处理

    Yii中的错误及异常处理 Yii已经默认已经在CApplication上实现了异常和错误的接管,这是通过php的set_exception_handler, set_error_handler实现的. ...

  4. iOS开发——新特性Swift篇&Swift 2.0 异常处理

    Swift 2.0 异常处理 WWDC 2015 宣布了新的 Swift 2.0. 这次重大更新给 Swift 提供了新的异常处理方法.这篇文章会主要围绕这个方面进行讨论. 如何建造异常类型? 在 i ...

  5. Swift中的异常处理

    swift中的异常处理 如果在调用系统某一个方法时,该方法最后有一个throws.说明该方法会抛出异常.如果一个方法会抛出异常,那么需要对该异常进行处理 *在swift中提供三种处理异常的方式 方式一 ...

  6. 再谈PHP错误与异常处理

    博客好久没有更新了,实在惭愧,最近在忙人生大事,哈哈!这段时间没有看什么新的东西,结合项目中遇到的PHP异常处理问题,我又重新梳理了之前模糊的概念,希望对大家理解PHP异常处理有所帮助. 请一定要注意 ...

  7. Golang错误和异常处理的正确姿势

    Golang错误和异常处理的正确姿势 错误和异常是两个不同的概念,非常容易混淆.很多程序员习惯将一切非正常情况都看做错误,而不区分错误和异常,即使程序中可能有异常抛出,也将异常及时捕获并转换成错误.从 ...

  8. 【PHP】解析PHP中的错误和异常处理

    目录结构: contents structure [-] 错误级别 自定义处理器 设置异常日志 自定义异常类 在这篇文章中,笔者将会阐述PHP中的异常处理,希望能够对你有所帮助. 1.错误级别 PHP ...

  9. ThinkPHP5.0源码学习之注册错误和异常处理机制

    在base.php文件中,用一句代码\think\Error::register();实现错误和异常处理机制的注册. // 注册错误和异常处理机制 \think\Error::register(); ...

随机推荐

  1. SQLite数据库的基本操作

    SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中.它是D.RichardHipp建立的公有领域项目.它的设计目标是嵌入式的,而且目前已经在很多嵌入式产 ...

  2. ubuntu apt-get update失败 解决方法

    ubuntu apt-get update失败 1.出现错误:E:Could not get lock /var/lib/apt/lists/lock - open (11: Resource tem ...

  3. Leetcode 198 House Robber

    You are a professional robber planning to rob houses along a street. Each house has a certain amount ...

  4. Servlet与JSP版本历史以及Tomcat支持的版本

    查询这个的关键字:Java EE的版本历史. JavaServer Pages (JSP) Java Servlet 参考: https://en.wikipedia.org/wiki/Java_EE ...

  5. ES5/ES6的区别研究(ECMAScript)

    我所理解的概念应该是语法的区别和特性的区别 这里是ECMAScript的解析http://baike.baidu.com/item/ECMAScript 参考: (ES6)http://es6.rua ...

  6. Jenkins环境拓扑及部署流程

    环境拓扑图: 部署流程:

  7. hdu5183 hash大法

    维护前缀和sum[i]=a[0]-a[1]+a[2]-a[3]+…+(-1)^i*a[i]枚举结尾i,然后在hash表中查询是否存在sum[i]-K的值.如果当前i为奇数,则将sum[i]插入到has ...

  8. HDU 1754 I Hate It

    I Hate It Time Limit: 9000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  9. K-D Tree

    这篇随笔是对Wikipedia上k-d tree词条的摘录, 我认为解释得相当生动详细, 是一篇不可多得的好文. Overview A \(k\)-d tree (short for \(k\)-di ...

  10. Django TemplateSyntaxError Could not parse the remainder: '()'

    返回的数据是列表集合,如 n [5]: a = set() In [6]: a.add((1, 3)) In [7]: a Out[7]: {(1, 3)} 在模板中使用方式如下: {% for ar ...