Swift学习之错误处理(一)
一,概述
错误处理:指对代码中的异常情况, 作出响应的过程。 swift
在运行时对错误的抛出、捕获、传递、操作提供了一级支持。
错误处理
以便做出相应的响应。例如: 从硬盘上读取文件时, 有多种原因会导致读取操作失败: 文件不存在、没有读取权限、文件格式不能正确编码等, 用不同的错误来区分这些状态, 可以让你的程序正确处理, 并能告诉用户失败的原因对于错误
表示, OC
中用NSError
, 而在swift
中, 用 Error在OC中处理异常的方法是,在方法中传入一个error的指针地址,方法执行后,如果有错误,内部就会给error赋值。OC中异常处理的效率很低,通常情况下当程序出现错误时我们才会主动的抛出异常。Swift是苹果在OC之后发布的新的语言,两种语言做的事情相同,但Swift做了很多的优化和改动,在异常处理方面,Swift提供了一套完整的异常处理。
⚠️注意:跟Java语言类似, Swift的异常并不是真正的程序崩溃, 而是程序运行的一个逻辑分支;Swift和Java捕获异常的时序也是一样的。当Swift运行时抛出异常后并没有被处理, 那么程序就会崩溃。
⚠️注意:苹果建议使用枚举作为异常类型(为什么不推荐用类或者结构体?答案是枚举数据类型本身就是分成若干种情况,很适合做逻辑分支判断条件)。
二,表示错误
在swift中, 使用遵守Error协议的类型来表示错误.
/// A type representing an error value that can be thrown. public protocol Error { } Error 协议实际上是空的, 只是用来表示遵守该协议的某类型可以用于错误处理
注意: 抛出不遵守Error
协议的类型时, 编译器会报错
枚举
特别适用于封装错误, 可以并利用关联值特性, 来关联相关的错误信息, 如
enum TestError: Error{
case ErrorOne (String)
case ErrorTwo (String)
case ErrorThree (String)
case ErrorOther (String)
}
//这里枚举是非常适合处理Error的数据类型
三,抛出错误
抛出函数:函数声明中标记有throws
关键字。 在函数声明的参数(...)
后,返回箭头->
前使用throws
关键字,表示此函数,此时该函数即为throwing
函数(抛出函数)。方法或初始化方法可以抛出错误。
类似这样:
-
抛出函数(throwing函数)
func canThrowErrors() throws -> String
-
非抛出函数
func cannotThrowErrors() -> String
抛出函数可以传播错误,通过抛出其中的错误到函数被调用的范围内。需要注意的是只有抛出函数可以传播错误。在非抛出函数内的抛出的错误必须在函数内处理。使用关键字
throw
来执行抛出错误的操作,throw
后边的类型必须遵守Error
协议, 否则报编译错误
类似这样:
func throwErrorFun(number: Int) throws { if number == 0 { print("OK") } else if number == 1 { throw TestError.ErrorOne("错误1") } else if number == 2 { throw TestError.ErrorTwo("错误2") } else if number == 3 { throw TestError.ErrorThree("错误3") } else { throw TestError.ErrorOther("其他") } }
Swift中的错误处理类似于其他语言中的异常处理,使用了try
,catch
和throw
关键字。但是与许多其他语言(包括Objective-C)不一样的是,Swift中的错误处理不涉及展示函数调用的堆栈信息。因此throw
与return
语句的性能相当。
使用
try
关键字调用抛出函数,以传播错误。
类似这样:
-
使用关键字
throw
来执行抛出错误enum VendingMachineError : Error { case InvalidGoods //!< 商品无效 case StockInsufficient //!< 库存不足 case CoinInsufficient(coinNeeded:Int) } struct Item { var price : Int var count : Int } class VendingMachine { var coins : Int = 0 var goods : [String : Item] = [ "果粒橙": Item.init(price: 3, count: 2), "红牛": Item.init(price: 5, count: 4), "雪碧": Item.init(price: 4, count: 6)] func sell(itemName name : String , itemCount count : Int) throws -> Void { guard let item = goods[name] else { throw VendingMachineError.InvalidGoods } guard item.count > 0 && item.count > count else { throw VendingMachineError.StockInsufficient } guard item.price * count <= coins else { throw VendingMachineError.CoinInsufficient(coinNeeded: item.price * count - coins) } //可以成功购买 coins -= item.price * count goods[name] = Item.init(price: item.price, count: item.count - count) } }
-
使用
try
关键字调用抛出函数,以传播错误。(这里的try仅起到传播错误的作用,并未在init,buy相应的throwing方法内部进行do-catch处理或者其它操作的错误)class Customer { var itemName : String var itemCount : Int var vendingMachine : VendingMachine //使用`try`关键字调用抛出函数,以传播错误 //可抛出的初始化方法 init(itemName:String,itemCount:Int,vendingMachine:VendingMachine) throws { try vendingMachine.sell(itemName: itemName, itemCount: itemCount) self.itemName = itemName self.itemCount = itemCount self.vendingMachine = vendingMachine } //可抛出函数 func buy() throws -> Void { try vendingMachine.sell(itemName: itemName, itemCount: itemCount) } }
四,处理错误
对于throwing
函数, 调用时必须要处理错误, 否则报编译错误: Errors thrown from here are not handled
-
使用
do-catch捕获/
处理错误 (try-do-catch)我们可以理解do-catch语句把try语句包裹起来,try后面必须接用throws 修饰的函数或方法,当这个方法发送错误并throw错误时,由catch语句来捕捉,并进行处理
do-catch
语句处理错误的形式:do { try expression //throw error语句 /// 没有错误发生时的后续语句 } catch pattern 1 { statements /// 捕捉到错误,处理错误 } catch pattern 2 where condition { statements /// 捕捉到错误,处理错误 } catch { statements /// 捕捉到错误,处理错误 }
进行错误处理的操作示例
class HandleError { class func test()->Void{ let vendmachine = VendingMachine() vendmachine.coins = 10 do { try vendmachine.sell(itemName: "红牛", itemCount: 5) print("购买成功") } catch VendingMachineError.InvalidGoods { print("购买失败" + "商品无效") } catch VendingMachineError.StockInsufficient { print("购买失败" + "库存不足") } catch VendingMachineError.CoinInsufficient(coinNeeded: let x){ print("购买失败" + "货币不足还需" + "\(x)个硬币") }catch{ print("购买失败") } } }
class HandleError { class func test()->Void{ let vendmachine = VendingMachine() vendmachine.coins = 10 do { try vendmachine.sell(itemName: "红牛", itemCount: 5) print("购买成功") } catch{ print("购买失败") } } }
判断捕获的错误类型:
class HandleError { class func test()->Void{ let vendmachine = VendingMachine() vendmachine.coins = 10 do { try vendmachine.sell(itemName: "红牛", itemCount: 5) print("购买成功") } catch is VendingMachineError { print("抛出的错误是VendingMachineError") } catch { print("抛出的错误不是VendingMachineError") } } }
注意:
catch
语句如果没有指定匹配的错误值, 默认会有一个本地的error
变量 -
将错误转换为可选值(try?)
使用
try?
将错误转换为可选值来处理。如果在评估try?
表达式时抛出了错误,则表达式的值为nil
。func someThrowingFunction() throws -> Int { // ... } let x = try? someThrowingFunction()
展开
try?
的写法:let y: Int? do { y = try someThrowingFunction() } catch { y = nil }
注意: 使用
try?
处理错误, 如果有返回值, 则不管返回值是什么类型, 都会加一层可选类型封装, 即Int
变成Int?
,Int?
变成Int??
-
断言错误(try! )
使用关键字
try!
, 可以禁用错误传递, 因为系统会封装一层没有错误抛出
的运行时断言, 如果有错误抛出, 则断言失败, 中断程序,报运行时错误let x = try! someThrowingFunction()
注意: 该方法适用于确定可以成功操作的情况, 使用时务必小心, 因为一旦判断不严谨, 会造成
app
崩溃
五,总结
表示错误: 使用遵守
Error
协议的类型, 通常多用枚举传递错误: 使用
throws
关键字, 声明函数为throwing
函数抛出错误: 使用
throw
关键字, 来执行抛出错误的操作转换错误: 使用
try?
关键字, 有错误,表达式则为nil,并对返回值进行可选类型封装断言错误: 使用
try!
关键字, 有错误,则中断程序, 有危险, 慎用!捕获错误: 使用
try
+do-catch
关键字, 有错误, 程序跳转到catch
; 无错误, 则继续执行do
语句块
-
try?
、try!
、try, do-catch
的区别
*
try?
: 尝试性的去做: 不管成功与否,都会正常执行下去, 不会打断程序执行流程, 有错误, 表达式就返回nil
而已*
try!
: 确定性的去做: 确定可以成功, 如果不成功, 则说明有问题, 报运行错误,程序终止*
try
+do-catch
: 负责任的去做:成功,则正常顺序执行;不成功, 对错误进行处理
注意: do-catch
中也可以使用try?
和try!
但这样做毫无意义, 因为try?
和try!
实际上已经对错误进行了处理, 所以catch
分支压根都不会被调用,也就相当于do{}
, 只是加了一个内部作用域