Swift 2.0之错误处理(上)

错误处理是 Swift 2.0 推出的新特性,如果想知道关于更多的 Swift 2.0 新特性,请参考我的另一篇文章:Swift 2.0 新特性。

在原来的 Cocoa 开发中,我们通常使用 NSError 对象来标识一个错误。我们可以为一个可能发生错误的方法传一个 NSError 对象的指针(在 Swift 当中是 inout 类型的参数),当这个方法发生错误时,它会对 NSError 对象进行赋值,我们便能获取错误的具体信息。这种方法的弊端在于,我们可以通过传入一个 nil 来忽略错误,或者在拿到错误对象后压根不对它进行处理。

Swift 2.0 加强了错误处理的安全性,我们可以使用 throws 关键字来表示一个方法可能抛出错误,这与 C++Java 中的异常处理很相似。通过抛出错误,我们可以将控制权转交给方法调用者。

当接收到调用方法抛出的错误时,调用者有两种选择:

  1. 将错误继续向上抛出,这意味着调用者不对错误进行处理。
  2. 接收并处理错误,这要使用到 Swift 2.0 的新关键字 catch。在接收到错误的同时,可以得到关联的错误对象,并获取错误的详细信息。

在 Swift 当中,错误对象必须遵守 ErrorType 协议。Cocoa 类库中使用 NSError 实例作为错误对象,而在自己的代码中,我们可以自定义错误类型。要自定义错误类型,只需要新建一个遵守了 ErrorType 协议的枚举类型,枚举中的每个值都代表不同的错误状态。由于 Swift 中的枚举能够使用关联值,这意味着我们能够为不同的错误状态提供关联的详细信息。

叨叨地讲了这么多理论知识,估计你也看烦了,说得再多,不如上代码来得直观。

先新建一个 playground,并输入下面的代码:

import UIKit

// 1
class BookShelf: CustomStringConvertible {
    // 有一个好大的书架
    private let maximumBooks = 1000
    private(set) var books: Int = 0
    
    var description: String {
        return "书柜当前还有\(books)本书"
    }
    
    // 2
    func purchaseBooks(count: Int) {
        books += count
        
        if books > maximumBooks {
            fatalError("你的书太多了,书柜已经放不下了")
        }
    }
    
    // 3
    func lendBooks(count: Int) {
        books -= count
        
        if books < 0 {
            fatalError("你都没书了,还要借给谁?")
        }
    }
}

// 4
func starterShelf() -> BookShelf {
    let myShelf = BookShelf()
    myShelf.purchaseBooks(100)
    return myShelf
}

这段代码很简单,而且还没有用到错误处理:

  1. 定义一个书架的类型,并遵守了 CustomStringConvertible 协议,用来方便地打印出这个类型的信息。
  2. 定义一个买书的方法,并判断书架是否还能放下刚买的新书。
  3. 定义一个借书的方法,并判断书架上是否还有剩余的书。
  4. 这个方法用来创建一个初始状态的书架。

接着,我们来将这段代码改造改造,好让它能使用错误处理。

首先,先定义一个错误处理类型,在 import 下面添加如下代码:

enum BookShelfError: ErrorType {
    case NoEnoughSpace(required: Int)
    case OutOfBooks
}

就像前面所说的,我们定义了一个枚举,并让它遵守了 ErrorType 协议。这里定义了两个错误状态,同时还为NoEnoughSpace 状态提供了一个关联值,用来指出还差几个空位。

接着,将买书与借书两个方法修改成如下:

func purchaseBooks(count: Int) throws {
    if books + count > maximumBooks {
        let required = (books + count) - maximumBooks
        throw BookShelfError.NoEnoughSpace(required: required)
    }
        
    books += count
}
    
func lendBooks(count: Int) throws {
    if count > books {
        throw BookShelfError.OutOfBooks
    }
        
    books -= count
}

修改后,starterShelf 方法会报错,暂时先不管它。

在上面的代码中,我们使用了两个新的关键字 throwsthrow,前一个关键字用来表明该方法可能抛出错误,后一个则在发生错误用来抛出错误。

其它修改都是很直观的,需要注意的一点是当抛出 NoEnoughSpace 错误时,还附带了一个关联值,这个值用来表示书柜缺少的空位。

再来看看 starterShelf 中的错误,因为这个函数调用了 purchaseBooks 方法,所以我们需要捕获错误,或者再次抛出错误。在这里,我们先将它抛出:

func starterShelf() throws -> BookShelf {
    let myShelf = BookShelf()
    try myShelf.purchaseBooks(100)
    return myShelf
}

对于一个可能抛出错误的方法,需要使用 try 关键字来调用它,由于要将错误再次抛出,所以同时使用了 throws 关键字来表明我们不准备处理这个错误。

但是,由于我们知道书架最多能放 1000 本书,这里的调用是绝对不会出现异常的。对于这种情况,我们可以用 forced-try 表达式,语法形式是在 try 后面加上感叹号,即 try!,表明我们知道方法调用不会出现异常,可以对其进行强制调用,同时也就不再需要指定 throws 关键字:

func starterShelf() -> BookShelf {
    let myShelf = BookShelf()
    try! myShelf.purchaseBooks(100)
    return myShelf
}

至此,所有的修改都已经完成了,接着就来试下这个类好不好使:

let shelf = starterShelf()

do {
    try shelf.purchaseBooks(5000)
} catch BookShelfError.NoEnoughSpace(let required) {
    print("书柜满啦,新买的书放不下了。还差\(required)个空位,赶紧买个新书柜吧")
}

shelf

在这段代码中,使用了 do-catch 语句来对错误进行捕获,在错误对象中还使用了关联值来取得所需要的书柜空位数。

执行以上代码,可以看到控制台打印出了异常信息,也就是调用 purchaseBooks 方法没有成功,此时,shelf 变量中的书本数应该还是 100。

再测试一下另一个方法:

do {
    // 这行可以顺利执行
    try shelf.lendBooks(50)
    // 这行会抛出一个错误
    try shelf.lendBooks(200)
} catch BookShelfError.OutOfBooks {
    print("书都没啦,你还借给谁啊")
}

shelf

这里的代码与上面的代码基本一样,对方法进行调用,然后捕获错误。可以看到 shelf 中的书本数是 50,所以可以确实,异常是在方法第二次调用时才抛出的。

完整的 playground 代码可以在 我的github 下载到。

你可能感兴趣的:(Swift 2.0之错误处理(上))