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

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(基础语法篇))