Swift 2.0之错误处理(上)

作者: 请叫我小锅 | 来源:发表于2015-07-03 21:10 被阅读3662次

    错误处理是 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之错误处理(上)

        本文链接:https://www.haomeiwen.com/subject/trryqttx.html