iOS - Swift 异常处理-阿里云开发者社区

开发者社区> QianChia> 正文

iOS - Swift 异常处理

简介: 前言 在 Swift 1.0 时代是没有异常处理和抛出机制的,如果要处理异常,要么使用 if else 语句或 switch 语句判断处理,要么使用闭包形式的回调函数处理,再要么就使用 NSError 处理。
+关注继续查看

前言

  • 在 Swift 1.0 时代是没有异常处理和抛出机制的,如果要处理异常,要么使用 if else 语句或 switch 语句判断处理,要么使用闭包形式的回调函数处理,再要么就使用 NSError 处理。以上这些方法都不能像 Java 中的 try catch 异常控制语句那样行如流水、从容不迫的处理异常,而且也会降低代码的可读性。当 Swift 2.0 到来后,一切都不一样了。在 Swift 2.0 中 Apple 提供了使用 throws、throw、try、do、catch 这五个关键字组成的异常控制处理机制。

  • 1)如何建造异常类型:

    • 在 iOS 开发当中,我们会面对很多异常处理。在 Cocoa Touch 中我们使用 NSError 来进行异常处理。在新的 Swift 2.0 中,我们可以使用新的 ErrorType protocol。

    • 在 Swift 中,enum 是最好的方法建立属于你自己的异常类型,你只要在你的 enum 中确认新的 ErrorType。

          enum MyError: ErrorType {
              case NotExist
              case OutOfRange
          }
  • 2)如何抛出异常:

    • 在抛出异常之前,我们需要在函数或方法的返回箭头 -> 前使用 throws 来标明将会抛出异常。

          func myMethodRetrunString() throws -> String
      
          // No return, we can just add throws in the end
          func myMethodRetrunNothing() throws
    • 声明之后,我们需要在函数或者方法里扔出异常,很简单使用 throw 就可以了。

          func myMethod() throws {
      
              // ...
      
              // item is an optional value
              guard let item = item else {
      
                  // need throws the error out
                  throw MyError.NotExist
              }
      
              // do with item
          }
    • 上面这段代码使用了 guard 来进行 unwrap optional value。这是 Swift 2.0 提供的一个新的方法。

  • 3)如何获取并处理异常:

    • 上文讲述了如何建造抛出异常,获取和处理异常就变得很简单了。使用 do-catch 机制。

          do {
      
              try functionWillThrowError()
      
          } catch {
      
              // deal with error
          }
    • do-catch 机制简单易懂。很多编程语言也使用类似的机制进行异常处理,但是在 Swift 中有一个比较重要的特性。catch 和 switch 一样具有 Pattern Matching 的能力。所以,使用 catch 你可以对异常的解析进行更为高级的处理。

          do {
      
              try functionWillThrowError()
      
          } catch MyError.NotExist {
      
              // deal with not exist
      
          } catch MyError.OutOfRange {
      
              // deal with not exist
          }
    • 这里值得提一下在 Swift 2.0 中一个跟异常处理没有关系的改进。Swift 2.0 中没有了 do-while 循环,取而代之的是 repeat-while。苹果说这个改动是为了增强代码的可读性。但是我更觉得是为了让我们更舒服的使用 do-catch。

  • 4)不处理异常:

    • 如果我不想处理异常怎么办,或者说,我非常确定某个方法或者函数虽然声明会抛出异常,但是我自己知道我在使用时候是绝对不会抛出任何异常的。这种情况下 我们可以使用 try!

          try! functionThrowErrorNil()
    • 当然,如果你使用 try!,而你的方法或者函数抛出了异常,那么你会得到一个运行中异常 (runtime error) 所以我们开发者需要慎用哦。

  • 5)总结:

    • 使用 ErrorType 的帮助建立你的异常类型。
    • 使用 throws 来声明异常,用 throw 来抛出异常。
    • 使用 do-catch 机制来获取和处理异常。

    • 下面我们来举例看看如何使用,我用使用手机刷朋友圈为例。首先我们需要定义异常枚举,在 Swift 2.0 中 Apple 提供了 ErrorType 协议需要我们自定义的异常枚举遵循:

          enum WechatError: ErrorType {
              case NoBattery                      // 手机没电
              case NoNetwork                      // 手机没网
              case NoDataStream                   // 手机没有流量
          }
    • 我们定义了导致不能刷微信的错误枚举 WechatError。然后定义一个检查是否可以刷微信的方法 checkIsWechatOk():

          func checkIsWechatOk(isPhoneHasBattery: Bool, isPhoneHasNetwork: Bool, dataStream: Int) throws {
      
              guard isPhoneHasBattery else {
                  throw WechatError.NoBattery
              }
      
              guard isPhoneHasNetwork else {
                  throw WechatError.NoNetwork
              }
      
              guard dataStream > 50 else {
                  throw WechatError.NoDataStream
              }
          }
    • 这里注意,在方法名后有 throws 关键字,意思为该方法产生的异常向上层抛出。在方法体内使用 guard 语句对各种状态进行判断,然后使用 throw 关键字抛出对应的异常。然后我们定义刷微信的方法:

          func playWechat(isPhoneHasBattery: Bool, isPhoneHasNetwork: Bool, dataStream: Int) {
      
              do {
                  try checkIsWechatOk(isPhoneHasBattery, isPhoneHasNetwork: isPhoneHasNetwork, dataStream: dataStream)
                  print("放心刷,刷到天昏地暗!")
              } catch WechatError.NoBattery {
                  print("手机都没电,刷个鬼啊!")
              } catch WechatError.NoNetwork {
                  print("没有网络哎,洗洗玩单机吧!")
              } catch WechatError.NoDataStream {
                  print("没有流量了,去蹭Wifi吧!")
              } catch {
                  print("见鬼了!")
              }
          }
      
          playWechat(true, isPhoneHasNetwork: true, dataStream: 60)       // 放心刷,刷到天昏地暗!
          playWechat(true, isPhoneHasNetwork: false, dataStream: 60)      // 没有网络哎,洗洗玩单机吧!
          playWechat(false, isPhoneHasNetwork: true, dataStream: 60)      // 手机都没电,刷个鬼啊!
          playWechat(true, isPhoneHasNetwork: true, dataStream: 30)       // 没有流量了,去蹭Wifi吧!
    • 上述的代码示例中,首先检查是否可以刷微信的方法前使用 try 关键字,表示允许该方法抛出异常,然后使用了 do catch 控制语句捕获抛出的异常,进而做相关的逻辑处理。

    • 这套异常处理机制使 Swift 更加的全面和安全,并且提高了代码的可读性,非常棒。

1、Guard

  • 在 Haskell, Erlang 等语言中早已存在 guard,在这里有更多关于它的介绍。guard 语句和 if 语句有点类似,都是根据其关键字之后的表达式的布尔值决定下一步执行什么。但与 if 语句不同的是,guard 语句只会有一个代码块,不像 if 语句可以 if else 多个代码块。guard 必须强制有 else 语句。guard 中的 else 只能执行转换语句,像 return, break, continue 或者 throws,当然你也可以在这里返回一个函数或者方法。那么 guard 语句的作用到底是什么呢?顾名思义,就是守护。guard 语句判断其后的表达式布尔值为 false 时,才会执行之后代码块里的代码,如果为 true,则跳过整个 guard 语句,我们举 例来看看。

  • 我们以今年高考为例,在进入考场时一般都会检查身份证和准考证,我们写这样一个方法:

        func checkup(person:[String:String!]) {
    
            // 检查身份证,如果身份证没带,则不能进入考场
            guard let id = person["id"] else {
                print("没有身份证,不能进入考场!")
                return
            }
    
            // 检查准考证,如果准考证没带,则不能进入考场
            guard let examNumber = person["examNumber"] else {
                print("没有准考证,不能进入考场!")
                return
            }
    
            // 身份证和准考证齐全,方可进入考场
            print("您的身份证号为:\(id),准考证号为:\(examNumber)。请进入考场!")
        }
    
        checkup(["id": "123456"])                              // 没有准考证,不能进入考场!
        checkup(["examNumber": "654321"])                      // 没有身份证,不能进入考场!
        checkup(["id": "123456", "examNumber": "654321"])      // 您的身份证号为:123456,准考证号为:654321。请进入考场!
  • 上述代码中的第一个 guard 语句用于检查身份证,如果检查到身份证没带,也就是表达式为 false 时,执行大括号里的代码,并返回。第二个 guard语 句则检查准考证。如果两证齐全,则执行最后一个打印语 句,上面的两个 guard 语句大括号内的代码都不会执行,因为他们表达式的布尔值都是 true。

  • 这里值得注意的是,id 和 examNumber 可以在 guard 语句之外使用,也就是说当 guard 对其表达式进行验证后,id 和 examNumber 可在整个方法的作用域中使用,并且是解包后的。

  • 我们再用 if else 语句写一个类似的方法:

        func checkupUseIf(person: [String: String!]) {
    
            if let id = person["id"], let examNumber = person["examNumber"] {
    
                print("您的身份证号为:\(id),准考证号为:\(examNumber)。请进入考场!")
    
            } else {
    
                print("证件不齐全,不能进入考场!")
            }
    
            print("您的身份证号为:\(id),准考证号为:\(examNumber)")     // 报异常
    
        }
    
        checkupUseIf(["id": "123456"])                             // 证件不齐全,不能进入考场!
        checkupUseIf(["examNumber": "654321"])                     // 证件不齐全,不能进入考场!
        checkupUseIf(["id": "123456", "examNumber": "654321"])     // 您的身份证号为:123456,准考证号为:654321。请进入考场!
  • 我们可以看到用 if else 实现的方法显然不如 guard 实现的那么精准。而且 id 和 examNumber 的作用域只限在 if 的第一个大括号内,超出这个作用域编译就会报错。

  • 通过上述两个小例子不难看出,guard 语句正如一个称职的守卫,层层把关,严防一切不允许发生的事,并且让代码具有更高的可读性,非常棒。

2、Defer

  • 在一些语言中,有 try/finally 这样的控制语句,比如 Java。这种语句可以让我们在 finally 代码块中执行必须要执行的代码,不管之前怎样的兴风作浪。在 Swift 2.0 中,Apple 提供了 defer 关键字,让我们可以实现同样的效果。

        func checkSomething() {
    
            print("CheckPoint 1")
    
            doSomething()
    
            print("CheckPoint 4")
    
        }
    
        func doSomething() {
    
            print("CheckPoint 2")
    
            defer {
                print("Clean up here")
            }
    
            print("CheckPoint 3")
    
        }
    
        // CheckPoint 1, CheckPoint 2, CheckPoint 3, Clean up here, CheckPoint 4
        checkSomething()                            
  • 上述示例可以看到,在打印出 “CheckPoint 2” 之后并没有打印出 “Clean up here”,而是 “CheckPoint 3”,这就是 defer 的作用,它对进行了 print("Clean up here") 延迟。我们再来看一个 I/O 的示例:

        // 伪代码
    
        func writeSomething() {
    
            let file = OpenFile()
    
            let ioStatus = fetchIOStatus()
    
            guard ioStatus != "error" else {
                return
            }
    
            file.write()
    
            closeFile(file)
        }
  • 上述示例是一个 I/O 操作的伪代码,如果获取到的 ioStatus 正常,那么该方法没有问题,如果 ioStatus 取到的是 error,那么会被 guard 语句抓到执行 return 操作,这样的话 closeFile(file) 就永远都不会执行了,一个严重的 Bug 就这样产生了。下面我们看看如何用 defer 来解决这个问题:

        // 伪代码
    
        func writeSomething() {
    
            let file = OpenFile()
    
            defer {
                closeFile(file)
            }
    
            let ioStatus = fetchIOStatus()
    
            guard ioStatus != "error" else {
                return
            }
    
            file.write()
        }
  • 我们将 closeFile(file) 放在 defer 代码块里,这样即使 ioStatus 为 error,在执行 return 前会先执行 defer 里的代码,这样就保证了不管发生什么,最后都会将文件关闭。

  • 在你的代码块就要结束前。如果你使用了 defer。在其之中的代码就会运行。等于说,给了你最后的机会来进行一些处理。如果你熟悉 BDD 或者 TDD,那么你可以参考他们中的 aferAll 机制。

        func myFunction() throws {
    
            defer {
    
                // No matter what happened I need do something
                print("All done, clean up here")
            }
            guard let item = item else {
    
                // need throws the error out
                throw MyError.NotExist
            }
            guard item.count > maxNumber else {
    
                // need throws the error out
                throw MyError.OutOfRange
            }
    
            // do something with item
            // ...
        }
  • 注意,如果你有多个 defer 语句,他们在执行的顺序会和栈一样,最后一个进,第一个出。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
8634 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
11000 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
10461 0
使用SSH远程登录阿里云ECS服务器
远程连接服务器以及配置环境
2290 0
使用OpenApi弹性释放和设置云服务器ECS释放
云服务器ECS的一个重要特性就是按需创建资源。您可以在业务高峰期按需弹性的自定义规则进行资源创建,在完成业务计算的时候释放资源。本篇将提供几个Tips帮助您更加容易和自动化的完成云服务器的释放和弹性设置。
11938 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
12274 0
腾讯云服务器 设置ngxin + fastdfs +tomcat 开机自启动
在tomcat中新建一个可以启动的 .sh 脚本文件 /usr/local/tomcat7/bin/ export JAVA_HOME=/usr/local/java/jdk7 export PATH=$JAVA_HOME/bin/:$PATH export CLASSPATH=.
4568 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
6625 0
+关注
QianChia
树的芳香由风决定,人生的芳香由自己决定!
222
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
《2021云上架构与运维峰会演讲合集》
立即下载
《零基础CSS入门教程》
立即下载
《零基础HTML入门教程》
立即下载