Swift2.0 beta5 后异常处理的一些补充
随着XCode7 GM在9月9号的推出,Swift2.0也要进入了GM了.随后的语法应该会逐渐的稳定下来了.其实在Swfit2.0 beta6后,Swift2对异常的处理又发生了一些变化,比如增加了一个try?关键字.
因此,这篇文章就再罗列一下现在Swift2.0异常处理的各种场景与方式.
首先是定义一个异常.
Swift2.0中 所有的异常类都是遵循ErrorType这个协议的.因此定义一个异常的最简单的方式就是使用枚举来实现一个这个协议:
|
|
在这个例子中我使用枚举定义了一个AppException的异常类,有两个case,并且借助于swift中强大的枚举类,IllegalFormatExceptioncase拥有一个Int类型的变量为coinsNeeded.
当然,你也可以像JAVA中那样,从异常的基类上继承一个自定义的异常类.
在JAVA中是这样写的:
|
|
相同的逻辑在swift2中是这样的:
|
|
需要注意的是,在Swift2.0 beta5之前 直接用结构体来实现ErrorType是会出错的.
必须在实现的结构体中增加_domian:String _code:Int 这两个计算属性才可以.这不知道是它内部实现有什么魔法了. 但是在beta5以后就不存在这个问题了,我们可以任意的实现ErrorType用来自定义异常类.
接着就是要使用异常了
定义完异常后,接下来肯定就是在我们的程序中使用异常了.
swift2.0中提供了 try try! try? do catch throws throw 这几个关键字来处理异常.我们一个一个的来说.
1.throw/throws
throw和throws这两个关键字 其实和JAVA中的语义是一样的. 在JAVA面试中 它们两个的区别 基本上算是必考的题.throw表示的是 在程序语句中抛出一个异常.而throws用于定义在方法的签名上,表示这个方法可能会抛出异常.
siwft2中的throws语法与JAVA中的稍有不同, 差别就在于它的位置,以及类型标示.
在JAVA中,throws是位于方法签名的最后的:访问限定符 返回类型 方法名(入参列表) throws 异常类型.
而在swift中throws更多的就只是一个标示,它并不写明要抛出的异常类型是什么,只是告诉调用者,我是要抛出异常的.因此,它的位置更靠前,是在返回参数类型之前: func 方法名(入参列表) throws ->返回类型
在程序语句中使用throw抛出异常的时候,同JAVA一样,抛出的应该是一个异常类型的实例.
如果是枚举类型,并且是没有参数的.那么就可以直接写上枚举值,比如:throw AppException.IllegalArgumentException,如果是有参数的枚举类型或者是结构体,那么在throw后就需要传入实例的值.比如:throw ServerException(code: "E0001") throw AppException.IllegalFormatException(coinsNeeded: 5)
2.do/catch
这个就是swift中的异常捕捉的语法.do代码块用于包裹一个代码块,其中可能会有要抛出异常的方法.catch代码块用于捕捉异常,并在它自身的代码块中处理异常
比如常规的用法:
|
|
这段代码就演示了 如果使用 do-catch 代码块来捕捉一个异常,以及顺带演示了如何抛出一个带有参数的枚举类型的异常. 上面的代码 捕捉了任意类型的异常,并且使用了一个 e 变量来表示它.这样在catch块中就可以使用这个异常了.
当然,你也可以定义多个catch块,每一个catch块都指定一个要处理的异常类型,这样的用法也是很常见的.
在swift2中当然也是支持的,例如:
|
|
需要注意的是catch代码块的顺序是从上到下匹配的.所以,通常我们会把越广泛的catch异常处理块定义到越后面. 上面的例子就很好的表现了这一点. 并且也演示了 如何接收带有参数的枚举类型的异常的值.
当然,还有一种情况就是捕捉自定义的异常结构体.它和捕捉枚举类型的异常其实是一样的.只是多了一个异常类型转换的过程:
|
|
如上面的代码所示, 当cass为true的时候,方法会抛出一个自定义的ServerException异常. 而捕获这个异常的代码为catch let er as ServerException,也就是 它定义了一个变量er并且转换为ServerException类型,来接收这个异常. 因此异常体中就可以直接使用结构体中的成员和方法了.
同样,catch代码块的顺序是从上到下匹配的.
另外一个值得注意的地方在于,我可以在catch块中继续的抛出异常,只要有地方继续的do-catch这个异常或者是方法签名可以抛出异常即可.
因此,这又说明了一个问题,就是 如果一个方法可以抛出异常,那么它就可以直接调用其他的可能会抛出异常的方法,而不需一定要使用do-catch来捕获异常.
这样就可以一层一层的把异常传递出去.比如:
|
|
try/try!/try?
剩下的就是这对三胞胎兄弟了. 他们的语义都是说明 他们后面的方法是可能要抛出异常的,我要尝试执行.
其中, try必须在do-catch语句中. 用于在do-catch语句中标识哪句话可能会抛出异常.
比如:
|
|
这句话就很清楚的表明,exceptionTest2()在调用的时候可能会抛出异常,我要尝试的执行这句方法.如果执行成功没有问题,那么就赋值给result.如果有问题,那么就抛出异常,由异常捕获块来处理异常.
但是,有时某个方法虽然它定义了可能抛出异常,但是在我们的使用场景中非常的确定这个方法里面是不会抛出异常的.那么我们就可以不使用 do-catch块来多此一举的捕获异常. 直接使用try!就可以了.
它就说明这个方法里面是不可能抛出异常的,我很肯定.比如:
|
|
注意上面这个例子中的方法签名上并没有标示throws可能抛出异常. 但是在调用exceptionTest2()方法的时候非常的确定是不会抛出异常的,因此就直接使用了try!即可.
在这种情况下,如果一旦调用的方法抛出了异常,那么整个程序就会直接崩溃,这一点需要特别的注意.
还剩下一个就是try? 这个是Swift2.0 beta6中新增加的语法.
它会尝试执行一个可能抛出异常的操作,如果操作执行成功,执行的结果就会包裹在一个可选值里面,如果操作执行失败(比如某个错误被抛出了),那么执行的结果就是nil,而error的内容会被抛弃.
这样做的好处是在结合if let,guard 或者map等链式处理的时候 非常的方便.
因为有些时候我们并不关心异常的原因,只需要知道结果是异常的就可以了.那么在这个情况下.在异常的时候,需要非常繁琐的do-catch语句来不断的包裹和处理异常,可能整个业务的处理逻辑都被异常处理而打乱了.比如:
|
|
而如果使用了try?,那么代码就被简化成了:
|
|
一句话就搞定了.简化了不少. 当然,按照上面的写法我们就无法知道他错误的信息了.
defer
除了上面所说的那些专门处理异常的关键字. 还有一个关键字defer可以配合do-catch语句来实现JAVA中try-catch-finally的逻辑. 也就是一段代码,无论成功执行还是异常捕获甚至是try-finally,它都要在最后执行. 这个在swift中 就是使用defer关键字来实现的.
比如上面的test3的例子,稍微改一下即可:
|
|
他就会在方法的执行最后打印一句finally!. 需要注意的是,如果你把defer{}放在了do{}块里面.那么它的执行顺序是先于catch代码块的.
具体更多关于defer的用法,可以参考我另外的一篇博客:Swift 2.0 新特性
总结
以上就是Swift2.0中异常处理的一些方面.用熟悉以后,确实很大大的方便我们的编程,写出更优雅的程序.