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)
}
-
favoriteSnacks
是一个字典,类型为[String:String]
,键对应人名,值对应顾客喜爱的货物名字。 - 函数传入参数有两个,
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块钱
- 首先实例花了一个贩卖机
vendingMachine
- 投入硬币金额为8元。
- 使用
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!
时切记不可马虎。
总结
初稿,之后进行内容补充以及修改。