異常處理基礎篇
只要我們在編程,就一定要面對錯誤處理的問題。其實,為了讓我們少犯錯誤,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)用了一個會拋出異常的方法,它把表達式的評估結果轉(zhuǎn)換為一個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。

如果我們把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)")

接下來?在下一段中,我們將向大家介紹多線程環(huán)境中的異常處理。