Swift學習筆記21——錯誤處理(Error Handling)

Swift的錯誤處理是用來處理運行時錯誤的。

當錯誤發生時,你可以選擇抓錯誤,或者繼續往上拋出錯誤。當一個運行時錯誤最終沒辦法處理的時候,程序就會崩潰。

Swift中有一個空的協議用來給用戶自定義錯誤。一般使用枚舉類實現這個協議來自定義錯誤。如下

enum ComputerError: ErrorType {
    case NoGameError
    case MemoryError
    case HardDiskError
}

這裏定義了三種error。當你要拋出一個error的時候,使用throw關鍵字。如下

throw ComputerError.NoGameError


可以拋出錯誤的函數的定義

func canThrowErrors() throws -> String
 
func cannotThrowErrors() -> String

利用上面的語法,我們定義一個Computer類,這個類有一個playGame方法,它會在某些情況下拋出錯誤

class Computer {
    var memory = 1024
    var hardDisk = 4000
    
    var games = [
        "War3": Requirement(memory: 512, HardDisk: 1500),
        "LOL": Requirement(memory: 1500, HardDisk: 3000),
        "My World": Requirement(memory: 256, HardDisk: 500)]
    
    func playGame(gameName: String) throws -> Bool{
        guard let game = self.games[gameName] else {
            throw ComputerError.NoGameError
        }
        guard game.HardDisk < self.hardDisk else {
            throw ComputerError.HardDiskError
        }
        guard game.memory < self.memory else {
            throw ComputerError.MemoryError
        }
        return true
    }
}


注意一點的是,如果你要調用能拋出錯誤的函數的時候,必須使用try、try?或者try!關鍵字在前面,如下

try myPC.playGame("War4")   //這句會因爲沒有War4這個遊戲而拋出NoGameError錯誤

下面是處理錯誤的部分。

當調用一個可以拋出錯誤的方法的時候,一般有三種處理方法。

第一種是調用者繼續向上拋出這個錯誤。這種情況下,這個調用者也必須是一個可以拋出錯誤的函數。如果最後沒處理這個錯誤,那麼程序崩潰。

第二種是使用do...catch語句對錯誤進行處理。

第三種是使用try?或try!調用會拋出錯誤的函數。


第一種方法舉例,再定義一個Person類,它的play方法裏面調用了Computer的playGame方法,然後繼續拋出這個錯誤。

class Person {
    var pc = Computer()
    func play() throws {
        try self.pc.playGame("SC2")
    }
}

第二種方法舉例,使用do...catch語句處理。在do範圍裏面,我們可以調用會產生錯誤的方法。接着的catch語句可以接上要處理的錯誤類型。

class Person {
    var pc = Computer()
    func play() {
        do {
            try self.pc.playGame("LOL")
            print("have fun")
        } catch ComputerError.NoGameError {
            print("NoGameError")
        } catch ComputerError.MemoryError {
            print("MemoryError")
        } catch ComputerError.HardDiskError {
            print("HardDiskError")
        } catch {
            print("other error",error)
        }
    }
}

       值得注意的是最後一個catch。這裏並沒有加上要處理的錯誤。這的catch會捕捉所有類型的錯誤。

       另外還有一個問題,如果不加上這個捕捉所有類型的catch語句。調用playGame的那句會報錯說catch沒有窮盡會拋出的錯誤。但是我只有三個錯誤,照理來講應該是已經窮盡的了。不知道是Swift的問題,還是我代碼哪裏有問題。但如果把這個調用放到main.swift中去,也是隻使用3個catch,就不會報錯。搞不懂。

我現在的處理方式是把這個play方法繼續寫成一個可以拋出錯誤的方法。只處理自定義的三種錯誤。其他錯誤往上拋

class Person {
    var pc = Computer()
    func play() throws {
        do {
            try self.pc.playGame("LOL")
            print("have fun")
        } catch ComputerError.NoGameError {
            print("NoGameError")
        } catch ComputerError.MemoryError{
            print("MemoryError")
        } catch ComputerError.HardDiskError {
            print("HardDiskError")
        }
    }
}

第三種是使用try?或try!調用會拋出錯誤的函數。

在官方文檔裏面,它原意是使用try?去處理一個會拋出錯誤的方法的返回值,如果這個方法拋出錯誤,那麼會得到nil。如果沒有錯誤,那麼將函數返回值包裝成可選類型後返回。

var myPC = Computer()
var result = try? myPC.playGame("War3")   //這裏playGame沒有拋出錯誤,所以返回了Bool可選類型,值爲true


try!的區別是它默認調用方法是不會報錯的,如果調用的方法報錯,那麼會得到運行時錯誤。即使你將這個try!寫到了一個會拋出錯誤的方法裏面,它也不會向上拋出這個錯誤。而是直接崩潰。

try? 、try!   和try 三者的區別:

try在一個會拋出錯誤的方法裏面,它會把產生的錯誤交由catch處理或者向上拋出。

try?是出錯的時候返回一個nil,屏蔽錯誤。沒錯的話,將結果包裝成一個可選類型返回。

try!是在沒錯的情況下返回函數返回值。出錯的情況下直接崩潰。錯誤不會再交給catch處理或者向上拋出。


所以如果在do catch裏面將try改爲try?或者try!,那麼會有一個警告說catch永遠不會執行。


defer語句

       熟悉java的朋友應該知道finally語句。這個是在try..catch裏面無論是否有錯誤,在退出try..catch範圍的時候,最後都會執行finally範圍內的代碼。一般是用來做一些諸如關閉文件的工作。因爲無論出不出錯,最後都必須關閉打開的文件。

       Swift也弄了一個defer語句,同樣也是想在最後做這樣的一些事情。但是這個defer遠遠沒有finally那麼好用。

先來開開defer的語法。學過java的朋友知道finally只能用在try..catch裏面。但是defer可以不用在do..catch裏面。並且defer裏面不能用return、break或者拋出錯誤的語句。

下面是一個沒有用在do..catch中的例子

func readFile() {
    defer {
        print("Close file")
    }
    print("Open file")
    print("Deal with file")
}
readFile()
//打印
//Open file
//Deal with file
//Close file

留意上面的打印順序,可以看出defer中的語句是在最後才執行的。單從這裏看,和設計初衷一致:在最後面才執行。
但是一旦將這個defer用到do..catch裏面,你就會覺得很噁心。

我們先來看一個和我們想象中一致的代碼,首先改寫上面用到的Person類。我們的原意是這樣的:Person的play方法調用了Computer的會產生錯誤的playGame方法。我們定義了一個result的可選變量,用於接收playGame方法的返回值。在退出do..catch的時候,會調用defer語句。我們在裏面判斷result是否爲nil。然後輸出心情。

class Person {
    var pc = Computer()
    func play() throws {
        do {
            var result: Bool?
            defer {
                if result != nil {
                    print("have fun")
                } else {
                    print("sad")
                }
            }
            result = try self.pc.playGame("LOL")
            print("playGame")
        } catch ComputerError.NoGameError {
            print("NoGameError")
        } catch ComputerError.MemoryError{
            print("MemoryError")
        } catch ComputerError.HardDiskError {
            print("HardDiskError")
        }
    }
}


然後我們執行下面代碼,因爲默認的Computer類的hardDisk不足。所以會報MemoryError錯誤。

var p = Person()
try p.play()
//打印
//sad
//MemoryError

注意打印順序,可以看出defer是先與catch執行的。這個和java的finally是不一樣的。finally是在catch執行完後執行。其次是playGame沒有打印,說明報錯之後的語句不會執行。

如果playGame的參數是“War3”,那麼程序不會報錯。輸出是

playGame
have fun
從以上來看,似乎還是符合我們的原意。

但是當你把defer的位置改改,變爲下面這樣

class Person {
    var pc = Computer()
    func play() throws {
        do {
            var result: Bool?
            result = try self.pc.playGame("LOL")
            defer {
                if result != nil {
                    print("have fun")
                } else {
                    print("sad")
                }
            }
            print("playGame")
        } catch ComputerError.NoGameError {
            print("NoGameError")
        } catch ComputerError.MemoryError{
            print("MemoryError")
        } catch ComputerError.HardDiskError {
            print("HardDiskError")
        }
    }
}

你會發現輸出只有一個MemoryError。也就是說當定義defer前面的語句報錯之後,defer得不到執行。這樣就要求這個defer必須寫在一個合理的位置纔行。


另外如果我們再改改上面的代碼,使用try!

class Person {
    var pc = Computer()
    func play()  {
        var result: Bool?
        defer {
            if result != nil {
                print("have fun")
            } else {
                print("sad")
            }
        }
        result = try! self.pc.playGame("LOL")
        print("playGame")
    }
}
這時候什麼都沒有打印,程序直接崩潰掉了。這個defer的初衷完全不同。


Swift的錯誤處理也是近幾個版本纔出現的。估計以後會繼續改進。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章