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的錯誤處理也是近幾個版本纔出現的。估計以後會繼續改進。