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的错误处理也是近几个版本才出现的。估计以后会继续改进。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值