在之前使用 Objective-C 的时候就对异常处理不太搞得明白,使用 swift 已经有一段时间了也是被异常处理经常困扰,因此特意学习了一下swift 中的异常处理,记录一下方便自己翻看。在其它相关语言中使用‘异常处理’这样的字样,但是在swift中很明显看到 Apple 使用 Error Handling,因此我们也使用‘错误处理’的字样来描述这个过程。
错误处理过程
大家经常会遇到在调用系统方法之前需要加一个 try! 关键字,这样经常会被弄的很郁闷。使用 try! 关键字是强行中断错误传递过程,如果在 Apple 的角度其实此时它使用一个 throw 关键字来抛出了一个错误因此 app 开发者需要捕捉这个错误。这里我把错误处理分为两个阶段。第一阶段是函数或者方法构造过程时如果遇到错误需要 throw 错误,第二阶段是函数调用者对错误进行捕捉或者传递。
错误类型
错误抛出之前总要对知道自己抛出的是个什么玩意吧,swift 中抛出的一般是执行 Error 协议的一个枚举。比如
enum VendingMachineError: Error {
case invalidSelection
case insufficientFunds(coinsNeeded: Int)
case outOfStock
}
抛出错误
错误抛出是在我们编写的函数、方法、初始化方法中,尤其需要注意需要在它们返回值之前用 throws 关键字标识该方法(函数、初始化方法)会抛出错误。在函数内部用 throw 关键字抛出。比如
// 1. 商品 Item(价格以及剩余数量)
struct Item {
var price: Int
var count: Int
}
// 2. 售货机器
class VendingMachine {
// 3. 商品库存
var inventory = [
"Candy Bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
// 4. 投入售货机的硬币数
var coinsDeposited = 0
// 5. 售货函数(通过商品名称卖出)
func vend(itemNamed name: String) throws {
guard let item = inventory[name] else {
// 6.商品名不存在抛出异常
throw VendingMachineError.invalidSelection
}
guard item.count > 0 else {
// 7.商品库存不足抛出异常
throw VendingMachineError.outOfStock
}
// 8.投入硬币不足抛出异常
guard item.price <= coinsDeposited else {
throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
// 9.购买成功没收与商品等价的硬币数量,哈哈!
coinsDeposited -= item.price
var newItem = item
newItem.count -= 1
// 10.更新该商品库存
inventory[name] = newItem
print("Dispensing \(name)")
}
}
以上代码段描述了错误的抛出,代码段介绍,时间有限不做过多介绍,应该很清晰了。
// 1. 商品 Item(价格以及剩余数量)
// 2. 售货机器
// 3. 商品库存
// 4. 投入售货机的硬币数
// 5. 售货函数(通过商品名称卖出)
// 6.商品名不存在抛出异常
// 7.商品库存不足抛出异常
// 8.投入硬币不足抛出异常
// 9.购买成功没收与商品等价的硬币数量,哈哈!
// 10.更新该商品库存
错误传递(我认为的)
比如在一个 拥有 throws 关键字标识的方法可以直接用 try 接一个可能会抛出错误的方法用来传递错误,比如下边
buyFavoriteSnack()
方法传递了以上 vendingMachine.vend()
方法的错误。
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy Bar"
try vendingMachine.vend(itemNamed: snackName)
}
当然也可以在错误传递的过程中进行错误捕捉但恰巧此时没把所有的错误给捕捉完成错误也是可以继续传递的,比如:
func nourish(with item: String) throws {
do {
try vendingMachine.vend(itemNamed: item)
} catch is VendingMachineError {
print("Couldn't buy that from the vending machine.")
}
}
do {
try nourish(with: "Beet-Flavored Chips")
} catch {
print("Unexpected non-vending-machine-related error: \(error)")
}
错误捕捉
调用一个会抛出错误的方法时需要使用 try, try?, try! 来标识方法进行错误捕捉。大家经常会头疼什么时候需要使用 try, try?, try! 。
-
try!(这样容易被打会抛出运行时错误 crash 程序)
任何时候都可以使用,这么做会使你的编译器快乐(但你的测试人员,老板,用户可能会感到不快乐。) -
try
常规捕捉错误的处理,需要配合do { }
catch {}
使用, catch 后需要跟上错误类型枚举值,比如
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
print("Success! Yum.")
} 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.")
} catch {
print("Unexpected error: \(error).")
}
- ** try?**(转换错误到可选值)
抛出错误的方法有返回值时可以使。如果调用方法时抛出错误则返回 nil, 正常返回(没抛出错误时)则返回方法的返回值。比如以下两种情形是等价的,以下代码段 x, y 变量返回值过程是等价的。
func someThrowingFunction() throws -> Int {
// ...
}
let x = try? someThrowingFunction()
let y: Int?
do {
y = try someThrowingFunction()
} catch {
y = nil
}
当然您也可以方便的进行可选值解包处理过程比如,
func fetchData() -> Data? {
if let data = try? fetchDataFromDisk() { return data }
if let data = try? fetchDataFromServer() { return data }
return nil
}
关闭错误传递
正如 try!可以中断错误传递比如,
let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
网友评论