总会报错:异常处理

这节课我们一起来认识一下 Swift 中的错误处理

在调用方法和写一个轮子的时候,总会有各种各样奇奇怪怪的错误,就是已经正常编译的软件,也会出现一些不可预期的错误。不过,这些错误当中,有一些是可以被识别和捕捉的——它们可预期。

可预期的错误

为什么我们说有一些错误是可以预料得到的呢?比如说读取一个文件的时候文件不存在、保存一个文档的时候目录不可写、下载文件的时候网络无连接、传送一个参数的时候范围超出控制……这些错误都是可以预料得到的!我们完全没有必要让程序在这些错误里崩溃!

那么,在这些情况下我们就需要一个完善的异常处理机制,把这些可以预料到的异常都给捕捉起来,用友好的方式处理掉,避免我们的程序栽在这些已知的错误上。

抛出错误和抓住错误

我们调用了一个方法,如果必要,那你必须要明白这个方法是有风险的,它有可能会执行失败——这个时候它就会将失败的原因抛出来——抛出错误。

你必须将它可能抛出错误的情况都考虑好,如果这样报错,怎么办,如果那样报错,又怎么办!

所以,如果我们创建的方法可能有风险,就一定要用 throws 标记出来,这样调用的时候我们就可以用 catch 来接住这个炸弹。

创建一个包含错误状态的枚举

我们用枚举类型来创建可能的错误状况,只需要让枚举遵循 ErrorType 协议即可,其他的都交给编译器完成。

比如说我们这里写两个错误,一个是枪管过热,另一个是没有子弹了!

有风险的方法

然后我们再来改造 Gun 的 fire() 方法:

一个简单的模型,每次发射之前检查弹药数量,检查枪管温度,如果都 OK,就射击,同时枪管温度增加,弹药量减少。(这里温度设置不一定合常理哈)

处理错误

然后我们来调用这个方法11次看看会有什么样的情况发生:

最终,我们的到的结果是:

Guard 是 if 吗?

答:不是。

如你所见,Guard 语法用起来好像很像是 if,但它不是,你可以理解为它是个反的 if , 如果我们反过来这样写也是完全可以的:

看起来是不是很绕?一堆一堆的大括号看着就眼晕!

使用 Guard 则就好像是个守门员——我们叫他“守门模式”,其实看起来就好像是反过来用 if:

这样虽然一样了,你注意到没有?我们把大于小于号给翻转了!因为这样才能让 if 起到我们想要的作用,或者,我们可以这样用?

这样大小的逻辑是摆正了,可是多出来的叹号更让人头大了,一不小心还容易扰不过这个弯儿……

所以,我们用 Guard,它也判断是非,如果“是”则不作用,如果“非”,就执行 else 里的内容,这样行内的逻辑对人来说就很直观了——不再需要曲线救国。

有风险的方法要放在 do – catch 块里

就像我前面的例子,我们把 fire 方法改造成能够抛出异常的方法,那么调用它的时候就必须把它放进特制的容器当中,这样编译器才能搞定和理解那些错误。

如上,我们在 do 的代码块当中来尝试方法(try),如果正常那皆大欢喜。如果方法抛出了异常,那我们就会执行到 catch 上——这里其实看起来好像 if else 的嵌套,也好像 switch 的选择——不过,catch 确实要求把可能会抛出的错误都写出来如果你不这么做,那么这个炸弹落地你的程序也就会被炸掉啦!

不过,就像 switch 一样,你也可以只写 catch 而不跟错误类型,这样就可以默认匹配所有错误了——缺点是你也不能知道发生了什么错误。

与调用者沟通

那我们做这个复杂的动作究竟有什么意义呢?直接把这些写成一个状态不更好?

还真不会更好。因为我们的方法并不一定就是给自己用,一个程序也不会只有一个人完成,当别人调用你的方法的时候,难道非得让人家先读一下800字的文档?在需要的位置抛出一个错误,写清楚错误的类型——调用者就可以方便地处理掉这个问题。

传球!

我们说遇到风险就要 try,而 try 出来的错误要 catch!可是没有 catch 住的炸弹怎么办?如果你不想拆弹呢?其实也可以,如果你在你的方法里调用了一个有风险的方法,而你又不想在这个方法里拆弹,那就不要管它就好了——当然,要记得把你的方法也标记为 throws ,这样这个炸弹会继续往下传!

呃,但你得记住,最终得有个方法来 catch 和拆弹,不然,你的程序还是会被炸掉。

如果一个问题得不到解决,它不会自行消失。

对了,如果你不想让错误抛出,那可以强制 try,使用关键字    try! ,这样就不需要拆弹了……因为遇到炸弹不用扔,直接炸。

发表评论

电子邮件地址不会被公开。 必填项已用*标注