美文网首页SwiftOptional Swift编程珠玑iOS开发
[Swift2.0系列]Error Handling(基础语法篇

[Swift2.0系列]Error Handling(基础语法篇

作者: NinthDay | 来源:发表于2015-09-18 20:28 被阅读398次

    Error Handling

    swift2.0时代到来,旧项目升级到最新语法恐怕已经让你焦头烂额。为此我正打算写一个关于swift2.0语法讲解以及实战中应用的博客系列。注意:仅仅是语法的改动,我早前已经在github.com中上传了改动日志,请点击这里。然而在实际改动项目时,依旧困难重重,甚至不知所措。今天带来的专题是Error Handling.

    0.前言

    • 文中将不涉及基础语法讲解,当然我会给出相关参考文档链接。
    • 以官方文档例程为主,当然我会给出详尽的注释,以及为什么这么做。

    1.如何呈现错误以及抛出错误

    以自动贩卖机为例,你可能在购买饮料时遇到以下几种失败情景:1.无效的选择 2.塞入的钱不足以买物品 3.贩卖机中货物售罄。现在来定义一个数据类型来描述错误,首先想到的便是枚举(enum),为此代码可以这么写:

    // 例程中只考虑以下三种情况 
    enum VendingMachineError{
        case InvalidSelection                       //无效选择
        case InSufficientFunds(coinsNeeded:Int) //金额不足
        case OutOfStock                             //货物售罄
    }
    

    这么写很直观,并且我们都喜欢用枚举来定义一些事物的不同情况。那么问题来了,凭什么上面这个枚举就是用来进行错误处理的,其他枚举则不是? 为此Swift中定义了ErrorType协议,而呈现错误的值类型都必须遵循这个协议,有趣的是这个协议内容是空的,意味着所有类型无须实现什么就算遵循这个协议了,你只需要在类型后加上:ErrorType(注:早前ErrorType并不能够让所有类型都遵循,现在则已经全部适用)。改动后的代码如下:

    // 例程中只考虑以下三种情况  切记加上ErrorType 标示它能够进行错误处理
    enum VendingMachineError:ErrorType{
        case InvalidSelection                       //无效选择
        case InsufficientFunds(coinsNeeded:Int) //金额不足
        case OutOfStock                             //货物售罄
    }
    

    现在我们可以来谈谈错误发生时的情况了。倘若塞入的钱不足以买货物,那么贩卖机就要给用户抛出一个错误,也就是VendingMachineError.InsufficientFunds(coinsNeeded:5),而这只是一个错误提示,那么这个动词呢?
    显然swift2.0中考虑到了,使用throw声明抛出错误,很形象不是吗。最后"抛出金额不足错误"用代码语句表述为throw VendingMachineError.InsufficientFunds(coinsNeeded:5)

    2.处理错误

    既然有错误抛出,自然对应有错误处理,那么swift2.0中是如何实现的呢?仍然以自动贩卖机为例,对贩卖机购买请求,同时注意捕获抛出的错误。这句话用代码描述即为:

    do{
        //对贩卖机试着进行选择饮料的动作,而这个动作可能会抛出错误
    }catch{
        //捕获抛出的错误 并执行相应措施
    }
    

    如此声明方式,在之后代码维护时,一眼就能知道这里会抛出错误要进行处理。现在我们知道在do大括号之中,我们需要尝试一些执行操作,比如调用一些函数,方法或者是构造函数,不过要求只有一个:以上这些东西必须能够抛出错误!!!那么如何声明一个能够抛出错误的函数,方法呢,请看下文。

    3.使用Throwing函数抛出错误

    swift中,早前我们定义一个函数,是这样的:

    func cannotThrowErrors()->String{
        // do something
    }
    

    从给函数的命名上就可得知该函数是无法抛出错误的,那么问题来了,能够产生并抛出错误的函数、方法是如何声明的呢?其实很简单,只需要在->前加上关键字throws即可,至于调用,则只需要使用try关键字即可(try?以及try!的用法在之后给出)。

    func canThrowErrors()throws ->String{
        //这里会将产生的错误抛出
    }
    //调用能够抛出错误的函数
    try canThrowErrors()
    

    切记:只有用throws关键字修饰的函数才能传递错误。而在正常函数中,只能处理抛出的错误。

    接着上文的贩卖机例程,为贩卖机中的购买项声明一个类,内容包括价格和数量:

    struct Item{
        var price:Int
        var count:Int
    }
    

    接着自动售货机声明一个类,如下:

    class VendingMachine{
        // 存货清单的
       var inventory = [
           "Candy Bar": Item(price: 12, count: 7),
           "Chips": Item(price: 10, count: 4),
           "Pretzels": Item(price: 7, count: 11)
       ]
       // 记录用户塞入的硬币数目
       var coinsDeposited = 0
       func dispenseSnack(snack:String){
        print("Dispensing \(snack)")
       }
    }
    

    VendingMachine贩卖机类中默认是有存货的,存储到inventory这个[Item]数组中,货物的价格和数量一目了然。接下来为贩卖机增添一个“出售”函数,根据用户输入的选项进行处理,显然这个函数是可以抛出错误的。因此在VendingMachine类中最后添加下面这个方法:

    //购物 传入货物名称
    func vend(itemNamed name: String) throws {
            // 通过商品名从清单(字典)中获取商品,假如我要的东西不存在贩卖机里 那么就要抛出错误,错误如下
            // 通过kvo来进行货物是否在清单内检查
            guard var item = inventory[name] else {
                //抛出错误:无效的选择
                throw VendingMachineError.InvalidSelection
            }
            //判断该商品的数量是否大于0
            guard item.count > 0 else {
                //抛出错误 售罄
                throw VendingMachineError.OutOfStock
            }
            // 塞入的硬币数目coinsDeposited是否足够负担一件项目的价格
            guard item.price <= coinsDeposited else {
                //钱不够 则要抛出这个金额不足的错误 并捎带差的金额信息
                throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
            }
            //OK 钱够了 买一件 更新贩卖机货品信息 并打印购买信息
            coinsDeposited -= item.price
            --item.count
            inventory[name] = item
            dispenseSnack(name)
        }
    

    现在贩卖机已经能实现简单的出售行为,是时候让顾客来购买一波了!

    // 1 
    // 类型:字典 [String:String] -> [人:各自喜欢的食物]
    let favoriteSnacks = [
        "Alice": "Chips",
        "Bob": "Licorice",
        "Eve": "Pretzels",
    ]
    // 2
    /*:
    * brief:这同样是一个能够抛出错误的函数 看throws关键字就一目了然
    * para: preson -> 购买点心的人名
    *       vendingMachine -> 贩卖机实例
    * return: none
    */
    func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
      //一样的道理 通过人名字来获取其喜欢的食物 假如人名不存在 那么默认是Candy Bar 注:??是解包的一种 自行了解
      let snackName = favoriteSnacks[person] ?? "Candy Bar"
      //OK 调用能够抛出错误的函数 要用try关键字
      try vend(itemNamed: snackName)
    }
    
    1. favoriteSnacks是一个字典,类型为[String:String],键对应人名,值对应顾客喜爱的货物名字。
    2. 函数传入参数有两个,person为顾客姓名,通过名字我们可以从favoriteSnacks中取出他/她喜爱的货物;vendingMachine是一个贩卖机实例。至于函数主体内容较为简单,见注释。

    4.使用Do-Catch来进行错误处理

    正如标题所给出的,我们将使用do-catch语句来进行错误处理,翻译成白话文也就是某件能够抛出错误的事情,同时时刻注意捕获抛出的错误进行处理。一般do-catch声明形式如下:

    do {
        try expression
        statements
    } catch pattern 1 {
        statements
    } catch pattern 2 where condition {
        statements
    }
    

    接下来进行对贩卖机的购买操作。

    // 1
    var vendingMachine = VendingMachine()
    // 2
    vendingMachine.coinsDeposited = 8
    // 3
    do {
        // 这里使用try关键字进行抛出错误函数的执行
        // 倘若抛出错误,会被下面catch体捕获到
        try buyFavoriteSnack("Alice", vendingMachine: vendingMachine)
    } catch VendingMachineError.InvalidSelection {
        print("Invalid Selection.")
    } catch VendingMachineError.OutOfStock {
        print("Out of Stock.")
    } catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
        print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
    }
    // prints "Insufficient funds. Please insert an additional 2 coins." //还差2块钱
    
    1. 首先实例花了一个贩卖机vendingMachine
    2. 投入硬币金额为8元。
    3. 使用do-catch来进行错误判断。可以看到灰常简单。

    5.try try? try!

    前文讲述了如何定义一个能够抛出错误的函数,只需要在->前添加throws关键字即可,形如:

    func someThrowingFunction() throws -> Int {
        // ...
    }
    

    当然也说明了使用try关键字来调用执行抛出异常函数,如try someThrowingFunction()。此处someThrowingFunction执行后有两种结果:正常执行返回一个Int结果值;执行失败抛出一个错误。显然我们对后者更感兴趣,要知道执行失败意味着结果值就不存在也就是等于nil。通过上文的学习,处理异常函数会这么写:

    let y:Int?
    do{
        y = try someThrowingFunction()
    }catch{
        y = nil
    }
    

    可以看到处理这种抛出错误时,返回值等于nil的处理情况,代码略长。swift自然也考虑到了这点,因此加入了try?
    上文代码只需一句代码即可代替let x = try? someThrowingFunction()

    如此可能还无法打动你的心,那么在举例来谈谈try?在实际应用中的优势。

    func fetchData() -> Data? {
        if let data = try? fetchDataFromDisk() { return data }
        if let data = try? fetchDataFromServer() { return data }
        return nil
    }
    

    该函数的职责是从磁盘或服务器读取数据,可以看到使用try?之后代码简洁易懂,倘若用do-catch,那滋味真是一个酸爽。

    显然使用try?处理抛出异常函数时,返回的是一个可选类型,需要进行解包对数据进行操作。确实这么做保证了类型安全,但是假如你已经百分百肯定该抛出异常函数不会抛出错误时,我们得到的值必定不为nil。那么try?只会加重之后操作的负担。为此我们可以使用try!对抛出异常函数处理,前提是你必须保证该函数绝不会抛出错误。

    let photo = try! loadImage("./Resources/John Appleseed.jpg")
    

    如上,你已经确保了图片链接地址是正确的,所以加载图片也肯定没有问题,不会抛出错误,那么使用try!执行这个函数,返回值为照片了,而非一个可选类型。当然马有失蹄,人有失足,万一你还是将链接地址写错了,必定会抛出错误,而你又使用了try!,不好意思,程序崩溃!所以在使用try!时切记不可马虎。

    总结

    初稿,之后进行内容补充以及修改。

    相关文章

      网友评论

        本文标题:[Swift2.0系列]Error Handling(基础语法篇

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