原文连接:《Error handling in Swift 2: try, catch, do and throw》
译文原链:Swift2 中的错误处理:try,catch,do 以及 throwios
若是你已经看了我那篇讨论 Swift2 中全部新东西的文章而且想了解更多关于新的错误处理系统的东西,这篇文章很是合适。简单来讲,它已经被彻底重写得现代化,快速和安全,而且除非你只使用 iOS API 的一小部分的话,你须要花些时间来学习一下。程序员
若是你喜欢这篇文章,你可能也会想读读这些:算法
What's new in iOS 9?swift
用于处理错误的历史方法是经过使用一个做为指针传递的 NSError 对象。在 Objective-C 中,它是 NSError*,但在 Swift 中你会看到 NSError? 和 NSErrorPointer。学习
当你调用一个可能失败的方法,你要传递一个空 NSError 做为参数,若是有问题的话这个参数就会被赋值。这让方法的返回值是你真正关心的那个数据。例如,在 Swift1.2 中,从硬盘加载一个 NSString 看起来是这样的:加密
var err: NSError? let contents = NSString(contentsOfFile: filePath, encoding: NSUTF8StringEncoding, error: &err) if err != nil { // uh-oh! }
这种编程风格在 Cocoa 中很是普遍,或者说至少:Swift2 彻底不这么干,因此上面的代码要么要重写要么就移除掉。es5
至于为何,有不少缘由。例如,用上面的调用方法,很容易就忽略了错误,要么是没有检查 err 的值,要么是根本就没用 NSError 而是直接传递了一个 nil。
虽然 Swift2 中的新错误处理须要多费点功夫,可是它让程序员阅读起来清楚明白得多,它抛弃了那些复杂的东西例如用 & 来传递 NSError,而且它经过保证你捕获全部错误来给你更高的安全性。
当你导入一个 Swift1.2 项目到 Xcode7 时,你会被问道是否想要将它转换成最新的 Swift 语法。它并不能生成和你手写的如出一辙的代码,可是它能帮你解决很大一部分工做,这样你就差很少肯定要去使用它了。
在上面那个从文件中加载字符串的例子中,它会将其转化为 Swift2 版本:
let contents: NSString? do { contents = try NSString(contentsOfFile: filePath, encoding: NSUTF8StringEncoding) } catch _ { contents = nil }
这里展现了五个你须要学习的新关键字其中三个。固然,严格来讲有一个不新,不过它的用法是新的:do 以前使用在 do … while 循环中,不过为了不混淆,在 Swift2 中,它已经被重命名为 repeat … while。
第四和第五个关键字是 throw 和 throws,咱们如今来更深刻地看看。
请建立一个新的 Xcode 项目,用单视图应用模板。随便命个名,随便选个目标设备 - 都不要紧,由于咱们此次不作任何跟视图有关的东西。
选择 ViewController.swift 而且添加这个新方法:
func encryptString(str: String, withPassword password: String) -> String { // complicated encryption goes here let encrypted = password + str + password return String(encrypted.characters.reverse()) }
这个方法会用传递进来的密码加密一个字符串。固然,它不会自动就这样作 - 这篇文章不是关于加密的,因此个人『加密』算法很悲剧:它将密码添加在输入的字符串先后,而后翻转这个字符串。你以后能够随意加上复杂的加密算法。
修改 viewDidLoad() 来调用这个方法:
let encrypted = encryptString("secret information!", withPassword: "12345") print(encrypted)
你如今运行你的应用,你将看到在 Xcode 终端上打印出了『54321!noitamrofni terces54321』。很简单对吧。
可是有一个问题:假设你实际上设定了一个有意义的加密算法,你没办法阻止用户输入一个空字符串做为密码,或者输入明显的密码相似『password』,或者甚至尝试在没有任何可加密数据的状况下调用加密算法。
Swift2 来帮忙了:你能够告诉 Swift 当这个方法发现它本身处于一个不可接受的状态时,它能够抛出一个错误,例如若是密码是六位或者更少位。这些错误是由你定义的,而后 Swift 用某种办法来保证你捕获全部的错误。
首先,咱们须要关键字 throws,你须要在定义你的方法时把它加在返回值前面,就像这样:
func encryptString(str: String, withPassword password: String) throws -> String { // complicated encryption goes here let encrypted = password + str + password return String(encrypted.characters.reverse()) }
一旦你这样作了,你的代码就会中止工做:添加 throws 命名让状况更糟了!不过它变糟了是由于一个好缘由:Swift 中的 try/catch 系统被设计为对开发者清晰明了,这意味着你须要用关键字 try 标记全部能够抛出错误的方法,就像这样:
let encrypted = try encryptString("secret information!", withPassword: "12345")
…不过即便如今你的代码仍是不能编译成功,由于你尚未告诉 Swift 当错误被抛出时要作什么。这就是关键字 do 和 catch 派上用场的地方:它们开始了一段可能运行失败的代码,而且处理那些失败。在咱们的简单例子里,它可能看起来是这样:
do { let encrypted = try encryptString("secret information!", withPassword: "12345") print(encrypted) } catch { print("Something went wrong!") }
这样全部的错误都没了,你的代码又能够运行了。不过目前为止它实际上尚未作任何有意思的事情,由于即便咱们说 encryptString() 可能抛出一个错误,它从没有真正发生。
在你能够抛出一个错误以前,你须要制做一个你要抛出的可能错误的列表。在咱们这个例子中,咱们要组织人们提供空密码,短密码和明显密码,不过以后你能够扩展它。
要作到这些,咱们须要建立一个枚举类型变量来表明咱们错误的类型。这须要创建在内建的 ErrorType 枚举类型上,不过无论怎样都很简单。把这个加载 ViewController 类的前面:
enum EncryptionError: ErrorType { case Empty case Short }
它定义了两个错误类型,而后咱们能够立刻开始用它们。由于它们是运行这个方法的前提条件,咱们要用这个新关键字 guard 来使咱们的意图清晰。
把这个放在 encryptString() 前面:
guard password.characters.count > 0 else { throw EncryptionError.Empty } guard password.characters.count >= 5 else { throw EncryptionError.Short }
若是你如今运行应用,没有什么变化,由于咱们在提供『12345』这个密码。不过若是你把它设置为一个空字符串,你会看到『Something went wrong!』在 Xcode 控制台打印出来了。
固然,有一个错误信息帮助不是很大 - 由于这个方法调用时有多种方式失败,而且咱们但愿给每一种状况提供一些有意义的信息。因此,把 viewDidLoad() 中的 try/catch 代码块改为这样:
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!") }
如今有了有意义的错误信息,咱们的代码开始看起来更棒了。不过你可能注意到了,虽然咱们已经捕获到了 .Empty 和 .Short 的状况,咱们还须要第三个 catch 代码块。
若是你还记得的话,我说过『Swift 经过一些方式来保证你捕获到全部错误』,这里咱们来讲明清楚:咱们已经能捕获全部咱们定义的错误,可是 Swift 还但愿咱们定义一个通常的 catch all 来处理任何其余可能出现的错误。咱们不用告诉 Swift 到底加密算法可能抛出哪一种错误,只须要说明它会抛出某些错误,所以这个额外的 catch-all 代码块是必须的。
有一个很差的地方:若是你新增任何值给枚举类型,它会直接进到默认的 catch 代码块 - 你不会被要求为它提供任何代码。
咱们将要给枚举类型加一个新的值来检测明显的密码。不过咱们将要用 Swift 超强枚举类型这样咱们能够返回一个带着错误类型的信息。所以,将 EncryptionError 枚举类型修改为这样:
enum EncryptionError: ErrorType { case Empty case Short case Obvious(String) }
如今当你想要抛出一个 EncrytionError.Obvious 类型的错误是,你必须提供一个理由。
guard password != "12345" else { throw EncryptionError.Obvious("I've got the same passcode on my luggage!") }
显然你不想写无数个 guard 声明来过滤出明显的密码,不过若是你记得如何使用 UITextChecker 来作拼写检查的话,就很方便了。
这就是完整的 Swift 基本 do/try/throw/catch 例子。你可能以为 try 声明没什么用,不过他是做为一个信号告诉开发者『这个调用可能失败』。这很重要:当一个 try 调用失败了,执行马上跳转到 catch 代码块,所以若是你看到一个调用以前的 try,它标志着底下的代码可能不会被执行。
还有一个要说的事情就是,若是你知道一个调用就是不会失败你该怎么作。如今,很显然这是一个你须要根据状况来作的决定,不过若是你知道有一个方法绝对不可能调用失败或者若是它调用失败的你的代码就会彻底崩溃,你可使用 try! 来告诉 Swift。
当你使用关键字 try!,你不须要用 do/catch 来包裹你的代码,由于你在保证它永远不会失败。你只须要这样写:
let encrypted = try! encryptString("secret information!", withPassword: "12345") print(encrypted)
使用关键字 try! 清楚地表达了你的意图:你知道理论上这个调用可能失败,可是你肯定它在你的用例中不会失败。例如,若是你从你的应用包中的文件中加载内容,任何失败意味着你的应用包被损坏了或者不可用,因此你须要终止应用。
这就是全部关于 Swift2 中错误处理的东西。若是你想学习 Swift 是怎样处理 try/finally,你应该读读我这篇关于关键词 defer 的文章。