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 语句,他们在执行的顺序会和栈一样,最后一个进,第一个出。

目录
相关文章
|
3月前
|
Swift iOS开发
iOS Swift使用Alamofire请求本地服务器报错-1002
iOS Swift使用Alamofire请求本地服务器报错-1002
102 1
|
4月前
|
Unix 调度 Swift
苹果iOS新手开发之Swift 中获取时间戳有哪些方式?
在Swift中获取时间戳有四种常见方式:1) 使用`Date`对象获取秒级或毫秒级时间戳;2) 通过`CFAbsoluteTimeGetCurrent`获取Core Foundation的秒数,需转换为Unix时间戳;3) 使用`DispatchTime.now()`获取纳秒级精度的调度时间点;4) `ProcessInfo`提供设备启动后的秒数,不表示绝对时间。不同方法适用于不同的精度和场景需求。
156 3
|
7天前
|
安全 Swift iOS开发
Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法
本文深入探讨了 Swift 与 UIKit 在 iOS 应用界面开发中的关键技术和实践方法。Swift 以其简洁、高效和类型安全的特点,结合 UIKit 丰富的组件和功能,为开发者提供了强大的工具。文章从 Swift 的语法优势、类型安全、编程模型以及与 UIKit 的集成,到 UIKit 的主要组件和功能,再到构建界面的实践技巧和实际案例分析,全面介绍了如何利用这些技术创建高质量的用户界面。
16 2
|
13天前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
35 9
|
23天前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户点击按钮时,按钮将从圆形变为椭圆形,颜色从蓝色渐变到绿色;释放按钮时,动画以相反方式恢复。通过UIView的动画方法和弹簧动画效果,实现平滑自然的过渡。
41 1
|
27天前
|
安全 API Swift
探索iOS开发中的Swift语言之美
【10月更文挑战第23天】在数字时代的浪潮中,iOS开发如同一艘航船,而Swift语言则是推动这艘船前进的风帆。本文将带你领略Swift的独特魅力,从语法到设计哲学,再到实际应用案例,我们将一步步深入这个现代编程语言的世界。你将发现,Swift不仅仅是一种编程语言,它是苹果生态系统中的一个创新工具,它让iOS开发变得更加高效、安全和有趣。让我们一起启航,探索Swift的奥秘,感受编程的乐趣。
|
1月前
|
Swift iOS开发 UED
如何使用Swift和UIKit在iOS应用中实现自定义按钮动画
【10月更文挑战第18天】本文通过一个具体案例,介绍如何使用Swift和UIKit在iOS应用中实现自定义按钮动画。当用户按下按钮时,按钮将从圆形变为椭圆形并从蓝色渐变为绿色;释放按钮时,动画恢复原状。通过UIView的动画方法和弹簧动画效果,实现平滑自然的动画过渡。
50 5
|
1月前
|
安全 Swift iOS开发
探索iOS开发中的Swift语言之美
在数字时代的浪潮中,移动应用已成为日常生活的延伸。本文将深入探讨iOS平台上的Swift编程语言,揭示其背后的设计哲学、语法特性以及如何利用Swift进行高效开发。我们将通过实际代码示例,展示Swift语言的强大功能和优雅简洁的编程风格,引导读者理解并运用Swift解决实际问题。
|
2月前
|
安全 Swift iOS开发
探索iOS开发之旅:Swift语言的魅力与挑战
【9月更文挑战第21天】在这篇文章中,我们将一起潜入iOS开发的海洋,探索Swift这门现代编程语言的独特之处。从简洁的语法到强大的功能,Swift旨在让开发者能够以更高效、更安全的方式构建应用程序。通过实际代码示例,我们会深入了解Swift如何简化复杂任务,并讨论它面临的挑战和未来的发展方向。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和知识。
46 4
|
2月前
|
安全 编译器 Swift
探索iOS开发之旅:Swift编程语言的魅力与挑战
【9月更文挑战第5天】在iOS应用开发的广阔天地中,Swift作为苹果官方推荐的编程语言,以其简洁、高效和安全的特点,成为了开发者的新宠。本文将带领你领略Swift语言的独特魅力,同时探讨在实际开发过程中可能遇到的挑战,以及如何克服这些挑战,成为一名优秀的iOS开发者。