Swift中的異常和錯誤處理—— 異常處理基礎篇

Swift中的異常和錯誤處理

泊學原文編程

只要咱們在編程,就必定要面對錯誤處理的問題。其實,爲了讓咱們少犯錯誤,Swift在設計的時候就儘量讓咱們明確感知錯誤,明確處理錯誤。例如:多線程

  • 只有使用Optional才能處理空值;app

  • switch...case...必須處理全部的請求;函數

總之,你到處能感覺到Swift爲你少犯錯的良苦用心。因此,當你真的要處理錯誤的時候,Swift固然更會要求你嚴謹處理。spa

如何描述一個錯誤?

在Swift裏,任何一個聽從ErrorType protocol的類型,均可以用於描述錯誤。ErrorType是一個空的protocol,它惟一的功能,就是告訴Swift編譯器,某個類型用來表示一個錯誤。而一般,咱們使用一個enum來定義各類錯誤。例如,假設咱們有一個機器人類型,咱們要定一個表達它工做狀態的錯誤:線程

enum RobotError: ErrorType {
    case LowPower(Double)
    case Overload(Double)
}

其中LowPower表示電量低,它的associated value表示電量的百分比。而Overload表示超過負載,它的associated value表示最大負載值。設計

如何描述一個會發生錯誤的方法?

而後,咱們來建立一個表示機器人的類:code

class Robot {
    var power = 1.0
    let maxLifting = 100.0 // Kg
}

它有兩個屬性,power表示當前電量,maxLifting表示它能夠舉起來的最大質量。而後,咱們添加一些能夠發送給Robot的命令:orm

enum Command {
    case PowerUp
    case Lifting(Double)
    case Shutdown
}

Command中的三個case分別表示對Robot發送:啓動、舉重和關機三個命令。ci

接下來,咱們給Robot添加一個接受命令的方法 action。

class Robot {
    var power = 1.0
    let maxLifting = 100.0 // Kg

    func action(command: Command) throws { }
}

因爲action有可能發生異常,對於這樣的方法,咱們要明確使用throws關鍵字標記它。在action的實現裏,咱們用一個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的實現裏,當處理.PowerUp命令時,咱們使用了guard確保Robot電量要大於20%,不然,咱們使用throw RobotError.LowPower(0.2)的方式拋出了一個異常(throw出來的類型必須是ErrorType)。

處理.Lifting命令時,咱們讀取了.Liftting的associated value,若是要舉起的質量大於maxLifting,則throw RobotError.Overload(maxLifting)。

一般,guard和throw配合在一塊兒,可讓咱們的代碼變的更加簡潔。

如何處理錯誤?

當咱們調用了一個可能會拋出異常的方法時,咱們必定要"經過某種方式"處理可能會發生的異常,若是你不處理,iOS會替你處理。固然,做爲"代勞"的成本,iOS也會Kill掉你的app。所以,對於"業務邏輯類"的異常,咱們仍是本身處理好些,Swift容許咱們使用三種方式處理異常。爲了演示它們的用法,咱們先來定義一個讓Robot工做的函數,因爲它會調用action,所以它也會拋出RobotError異常,咱們也須要用throws來定義它:

func working(robot: Robot) throws {
}

do...catch...

在working的實現裏,首先,咱們要讓Robot"啓動":

func working(robot: Robot) throws {
    do {
        try robot.action(Command.PowerUp)
    }
    catch let RobotError.LowPower(percentage) {
        print("Low power: \(percentage)")
    }
}

經過前面action的代碼咱們知道,若是傳入的robot參數的"電量"低於20%,action會拋出異常,所以在working的實現裏:

  • 咱們必須在調用會拋出異常的方法前面使用try關鍵字;

  • 若是咱們要捕獲方法拋出的異常,就須要把會拋出異常的代碼放在關鍵字do包含的代碼塊裏;

  • 咱們使用catch關鍵字匹配要捕捉的各類異常,例如在上面的例子裏,咱們捕捉了.LowPower,而且讀取了它的associated value;

若是咱們要捕獲多個異常,就能夠在do代碼塊後面,串聯多個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後面多串聯一個catch,用來捕獲Robot"超載"的異常。

錯、不錯都會執行的代碼

在Swift的異常處理機制理,有一個容許咱們添加不管代碼執行正常與否,只要離開當前做用域,就必定會執行的代碼。咱們使用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咱們肯定這個調用不會發生異常(若是你對Swift說謊,是會引起運行時異常的 ^.^)。

另外,使用"try!"標記的函數調用,能夠不放在do代碼塊裏。

把錯誤變成一個Optional

最後,咱們調用working函數,讓Robot完成工做:

let iRobot = Robot()
try? working(iRobot)

在這裏,咱們咱們使用了"try?"的形式調用了一個會拋出異常的方法,它把表達式的評估結果轉換爲一個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
}

從上面的代碼裏能夠看到,當函數有返回值的時候,咱們要把throws寫在返回值前面。

而後,咱們查看working的返回值和類型:

let a = try? working(iRobot)
print("value: \(a)\n type: \(a.dynamicType)")

這裏,因爲咱們處理異常,所以a的值是0,可是,a的類型,是一個Optional<Int>。
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)")

bo-reading-nil-working-ret@2x.jpg接下來?在下一段中,咱們將向你們介紹多線程環境中的異常處理。

相關文章
相關標籤/搜索