異常處理基礎篇
只要我們在編程像街,就一定要面對錯誤處理的問題。其實抖僵,為了讓我們少犯錯誤鲤看,Swift在設計的時候就盡可能讓我們明確感知錯誤,明確處理錯誤耍群。例如:
-只有使用Optional才能處理空值义桂;
-switch...case...必須處理所有的請求找筝;
總之,你處處能感受到Swift為你少犯錯的良苦用心慷吊。所以袖裕,當你真的要處理錯誤的時候,Swift當然更會要求你嚴謹處理溉瓶。
如何描述一個錯誤急鳄?
在Swift里,任何一個遵從ErrorType protocol的類型嚷闭,都可以用于描述錯誤攒岛。ErrorType是一個空的protocol,它唯一的功能胞锰,就是告訴Swift編譯器灾锯,某個類型用來表示一個錯誤。而通常嗅榕,我們使用一個enum來定義各種錯誤顺饮。例如,假設我們有一個機器人類型凌那,我們要定一個表達它工作狀態(tài)的錯誤:
enum RobotError: ErrorType {
case LowPower(Double)
case Overload(Double)}
其中LowPower表示電量低兼雄,它的associated value表示電量的百分比。而Overload表示超過負載帽蝶,它的associated value表示最大負載值赦肋。
如何描述一個會發(fā)生錯誤的方法?
然后励稳,我們來創(chuàng)建一個表示機器人的類:
class Robot {
var power = 1.0
let maxLifting = 100.0 // Kg
}
它有兩個屬性佃乘,power表示當前電量,maxLifting表示它可以舉起來的最大質(zhì)量驹尼。然后趣避,我們添加一些可以發(fā)送給Robot的命令:
enum Command {
case PowerUp
case Lifting(Double)
case Shutdown
}
Command中的三個case分別表示對Robot發(fā)送:啟動、舉重和關機三個命令新翎。接下來程帕,我們給Robot添加一個接受命令的方法 action。
class Robot {
var power = 1.0
let maxLifting = 100.0 // Kg
func action(command: Command) throws { }
}
由于action有可能發(fā)生異常地啰,對于這樣的方法愁拭,我們要明確使用throws關鍵字標記它。在action的實現(xiàn)里亏吝,我們用一個switch...case來遍歷Command:
class Robot {
var power = 1.0
let maxLifting = 100.0 // Kg
func action(command: Command) throws {
switch command {
case .PowerUp:
guard self.power > 0.2 else {
throw RobotError.LowPower(0.2)
}
print("Robot started")
case let .Lifting(weight):
guard weight <= maxLifting else {
throw RobotError.Overload(maxLifting)
}
print("Lifting weight: \(weight) KG")
case .Shutdown:
print("Robot shuting down...")
}
}
}
在action的實現(xiàn)里岭埠,當處理.PowerUp命令時,我們使用了guard確保Robot電量要大于20%,否則枫攀,我們使用throw RobotError.LowPower(0.2)的方式拋出了一個異常(throw出來的類型必須是ErrorType)。
處理.Lifting命令時株茶,我們讀取了.Liftting的associated value来涨,如果要舉起的質(zhì)量大于maxLifting,則throw RobotError.Overload(maxLifting)启盛。
通常蹦掐,guard和throw配合在一起,可以讓我們的代碼變的更加簡潔僵闯。
如何處理錯誤卧抗?
當我們調(diào)用了一個可能會拋出異常的方法時,我們一定要"通過某種方式"處理可能會發(fā)生的異常鳖粟,如果你不處理社裆,iOS會替你處理。當然向图,作為"代勞"的成本泳秀,iOS也會Kill掉你的app。因此榄攀,對于"業(yè)務邏輯類"的異常嗜傅,我們還是自己處理好些,Swift允許我們使用三種方式處理異常檩赢。為了演示它們的用法吕嘀,我們先來定義一個讓Robot工作的函數(shù),由于它會調(diào)用action贞瞒,因此它也會拋出RobotError異常偶房,我們也需要用throws來定義它:
func working(robot: Robot) throws {
}
do...catch...
在working的實現(xiàn)里,首先憔狞,我們要讓Robot"啟動":
func working(robot: Robot) throws {
do {
try robot.action(Command.PowerUp)
}
catch let RobotError.LowPower(percentage){
print("Low power: \(percentage)")
}
}
通過前面action的代碼我們知道蝴悉,如果傳入的robot參數(shù)的"電量"低于20%,action會拋出異常瘾敢,因此在working的實現(xiàn)里:
-我們必須在調(diào)用會拋出異常的方法前面使用try關鍵字拍冠;
-如果我們要捕獲方法拋出的異常,就需要把會拋出異常的代碼放在關鍵字do包含的代碼塊里簇抵;
-我們使用catch關鍵字匹配要捕捉的各種異常庆杜,例如在上面的例子里,我們捕捉了.LowPower碟摆,并且讀取了它的associated value晃财;
如果我們要捕獲多個異常,就可以在do代碼塊后面,串聯(lián)多個catch断盛,例如罗洗,我們添加一個讓Robot舉起某個東西的命令:
func working(robot: Robot) throws {
do {
try robot.action(Command.PowerUp)
try robot.action(Command.Lifting(52))
}
catch let RobotError.LowPower(percentage) {
print("Low power: \(percentage)")
}
catch let RobotError.Overload(maxWeight) {
print("Overloading, max \(maxWeight) KG is allowd")
}
}
我們就需要在do后面多串聯(lián)一個catch,用來捕獲Robot"超載"的異常钢猛。
錯伙菜、不錯都會執(zhí)行的代碼
在Swift的異常處理機制理,有一個允許我們添加無論代碼執(zhí)行正常與否命迈,只要離開當前作用域贩绕,就一定會執(zhí)行的代碼。我們使用defer關鍵字來指定這樣的代碼壶愤。例如淑倾,我們給working添加一個defer,它用來讓Robot關機征椒。
func working(robot: Robot) throws {
defer {
try! robot.action(Command.Shutdown)
}
do {
try robot.action(Command.PowerUp)
try robot.action(Command.Lifting(52))
}
catch let RobotError.LowPower(percentage) {
print("Low power: \(percentage)")
}
catch let RobotError.Overload(maxWeight) {
print("Overloading, max \(maxWeight) KG is allowd")
}
}
斷言肯定不會錯噠~
在上面的defer代碼塊里娇哆,我們使用了"try!"這樣的形式。這是由于defer代碼塊中陕靠,不允許我們包含任何會跳出當前代碼塊的語句迂尝,例如:break / return / 拋出異常等。因此剪芥,我們使用try!告訴Swift我們確定這個調(diào)用不會發(fā)生異常(如果你對Swift說謊垄开,是會引發(fā)運行時異常的 .)。
另外税肪,使用"try!"標記的函數(shù)調(diào)用溉躲,可以不放在do代碼塊里。
把錯誤變成一個Optional
最后益兄,我們調(diào)用working函數(shù)锻梳,讓Robot完成工作:
let iRobot = Robot()
try? working(iRobot)
在這里,我們我們使用了"try?"的形式調(diào)用了一個會拋出異常的方法净捅,它把表達式的評估結果轉換為一個Optional疑枯。例如,我們讓working返回一個Int:
func working(robot: Robot) throws -> Int {
defer {
try! robot.action(Command.Shutdown)
}
do {
try robot.action(Command.PowerUp)
try robot.action(Command.Lifting(52))
}
catch let RobotError.LowPower(percentage) {
print("Low power: \(percentage)")
}
catch let RobotError.Overload(maxWeight) {
print("Overloading, max \(maxWeight) KG is allowd")
}
return 0
}
從上面的代碼里可以看到蛔六,當函數(shù)有返回值的時候荆永,我們要把throws寫在返回值前面。
然后国章,我們查看working的返回值和類型:
let a = try? working(iRobot)
print("value: \(a)\n type: \(a.dynamicType)")
這里具钥,由于我們處理異常,因此a的值是0液兽,但是骂删,a的類型,是一個Optional。
![enter image description here](https://dn-boxueio.qbox.me/bo-reading-normal-working-ret@2x.jpg)
如果我們把RobotError.Overload注釋掉宁玫,然后讓Robot舉起超過100KG的物體:
func working(robot: Robot) throws -> Int {
defer {
try! robot.action(Command.Shutdown)
}
do {
try robot.action(Command.PowerUp)
try robot.action(Command.Lifting(152))
}
catch let RobotError.LowPower(percentage) {
print("Low power: \(percentage)")
}
/*catch let RobotError.Overload(maxWeight) {
print("Overloading, max \(maxWeight) KG is allowd")
}*/
return 0}
這樣異常就會被拋到working外圍粗恢,此時Swift運行時會捕捉到這個異常,并且欧瘪,把a的值設置成nil:
let a = try? working(iRobot)
print("value: \(a)\n type: \(a.dynamicType)")
![enter image description here](https://dn-boxueio.qbox.me/bo-reading-nil-working-ret@2x.jpg)
接下來适滓?在下一段中,我們將向大家介紹多線程環(huán)境中的異常處理恋追。