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")
}
}
}
另外如果我们再改改上面的代码,使用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的错误处理也是近几个版本才出现的。估计以后会继续改进。