【译】Swift2 中的错误处理:try,catch,do 以及 throw
Date: 2016-05-15 23:14:40
如果你已经看了我那篇讨论 Swift2 中所有新东西的文章并且想了解更多关于新的错误处理系统的东西,这篇文章非常合适。简单来说,它已经被完全重写得现代化,快速和安全,并且除非你只使用 iOS API 的一小部分的话,你需要花些时间来学习一下。
如果你喜欢这篇文章,你可能也会想读读这些:
- What’s new in Swift 2.2?
- What’s new in iOS 9?
- My free Swift tutorial series
- Pre-order Pro Swift for just $20!
过去是怎样:NSError 和 NSErrorPointer
用于处理错误的历史方法是通过使用一个作为指针传递的 NSError 对象。在 Objective-C 中,它是 NSError*,但在 Swift 中你会看到 NSError? 和 NSErrorPointer。
当你调用一个可能失败的方法,你要传递一个空 NSError 作为参数,如果有问题的话这个参数就会被赋值。这让方法的返回值是你真正关心的那个数据。例如,在 Swift1.2 中,从硬盘加载一个 NSString 看起来是这样的:
1 | var err: NSError? |
这种编程风格在 Cocoa 中非常广泛,或者说至少:Swift2 完全不这么干,所以上面的代码要么要重写要么就移除掉。
至于为什么,有很多原因。例如,用上面的调用方法,很容易就忽略了错误,要么是没有检查 err 的值,要么是根本就没用 NSError 而是直接传递了一个 nil。
虽然 Swift2 中的新错误处理需要多费点功夫,但是它让程序员阅读起来清楚明白得多,它抛弃了那些复杂的东西例如用 & 来传递 NSError,并且它通过保证你捕获所有错误来给你更高的安全性。
Swift2 中的方法:try,catch,do 以及 throw
当你导入一个 Swift1.2 项目到 Xcode7 时,你会被问道是否想要将它转换成最新的 Swift 语法。它并不能生成和你手写的一模一样的代码,但是它能帮你解决很大一部分工作,这样你就差不多确定要去使用它了。
在上面那个从文件中加载字符串的例子中,它会将其转化为 Swift2 版本:
1 | let contents: NSString? |
这里展示了五个你需要学习的新关键字其中三个。当然,严格来说有一个不新,不过它的用法是新的:do 之前使用在 do … while 循环中,不过为了避免混淆,在 Swift2 中,它已经被重命名为 repeat … while。
第四和第五个关键字是 throw 和 throws,我们现在来更深入地看看。
请创建一个新的 Xcode 项目,用单视图应用模板。随便命个名,随便选个目标设备 - 都没关系,因为我们这次不做任何跟视图有关的东西。
选择 ViewController.swift 并且添加这个新方法:
1 | func encryptString(str: String, withPassword password: String) -> String { |
这个方法会用传递进来的密码加密一个字符串。当然,它不会自动就这样做 - 这篇文章不是关于加密的,所以我的『加密』算法很悲剧:它将密码添加在输入的字符串前后,然后翻转这个字符串。你之后可以随意加上复杂的加密算法。
修改 viewDidLoad() 来调用这个方法:
1 | let encrypted = encryptString("secret information!", withPassword: "12345") |
你现在运行你的应用,你将看到在 Xcode 终端上打印出了『54321!noitamrofni terces54321』。很简单对吧。
但是有一个问题:假设你实际上设定了一个有意义的加密算法,你没办法阻止用户输入一个空字符串作为密码,或者输入明显的密码类似『password』,或者甚至尝试在没有任何可加密数据的情况下调用加密算法。
Swift2 来帮忙了:你可以告诉 Swift 当这个方法发现它自己处于一个不可接受的状态时,它可以抛出一个错误,例如如果密码是六位或者更少位。这些错误是由你定义的,然后 Swift 用某种办法来保证你捕获所有的错误。
首先,我们需要关键字 throws,你需要在定义你的方法时把它加在返回值前面,就像这样:
1 | func encryptString(str: String, withPassword password: String) throws -> String { |
一旦你这样做了,你的代码就会停止工作:添加 throws 命名让情况更糟了!不过它变糟了是因为一个好原因:Swift 中的 try/catch 系统被设计为对开发者清晰明了,这意味着你需要用关键字 try 标记所有可以抛出错误的方法,就像这样:
1 | let encrypted = try encryptString("secret information!", withPassword: "12345") |
do {
let encrypted = try encryptString(“secret information!”, withPassword: “12345”)
print(encrypted)
} catch {
print(“Something went wrong!”)
}
1 | 这样所有的错误都没了,你的代码又可以运行了。不过目前为止它实际上还没有做任何有意思的事情,因为即使我们说 encryptString() 可能抛出一个错误,它从没有真正发生。 |
enum EncryptionError: ErrorType {
case Empty
case Short
}
1 | 它定义了两个错误类型,然后我们可以马上开始用它们。因为它们是运行这个方法的前提条件,我们要用这个新关键字 guard 来使我们的意图清晰。 |
guard password.characters.count > 0 else { throw EncryptionError.Empty }
guard password.characters.count >= 5 else { throw EncryptionError.Short }
1 | 如果你现在运行应用,没有什么变化,因为我们在提供『12345』这个密码。不过如果你把它设置为一个空字符串,你会看到『Something went wrong!』在 Xcode 控制台打印出来了。 |
do {
let encrypted = try encryptString(“secret information!”, withPassword: “”)
print(encrypted)
} catch EncryptionError.Empty {
print(“You must provide a password.”)
} catch EncryptionError.Short {
print(“Passwords must be at least five characters, preferably eight or more.”)
} catch {
print(“Something went wrong!”)
}
1 | 现在有了有意义的错误信息,我们的代码开始看起来更棒了。不过你可能注意到了,虽然我们已经捕获到了 .Empty 和 .Short 的情况,我们还需要第三个 catch 代码块。 |
enum EncryptionError: ErrorType {
case Empty
case Short
case Obvious(String)
}
1 | 现在当你想要抛出一个 EncrytionError.Obvious 类型的错误是,你必须提供一个理由。 |
guard password != “12345” else { throw EncryptionError.Obvious(“I’ve got the same passcode on my luggage!”) }
1 | 显然你不想写无数个 guard 声明来过滤出明显的密码,不过如果你记得如何使用 UITextChecker 来做拼写检查的话,就很方便了。 |
let encrypted = try! encryptString(“secret information!”, withPassword: “12345”)
print(encrypted)
```
使用关键字 try! 清楚地表达了你的意图:你知道理论上这个调用可能失败,但是你确定它在你的用例中不会失败。例如,如果你从你的应用包中的文件中加载内容,任何失败意味着你的应用包被损坏了或者不可用,所以你需要终止应用。
这就是所有关于 Swift2 中错误处理的东西。如果你想学习 Swift 是怎样处理 try/finally,你应该读读我这篇关于关键词 defer 的文章。