原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ErrorHandling.html#//apple_ref/doc/uid/TP40014097-CH42-ID508

参考:http://wiki.jikexueyuan.com/project/swift/chapter2/18_Error_Handling.html

1、错误处理

错误处理是响应错误并从错误中返回的过程。swift提供一流错误支持,包括在运行时抛出,捕获,传送和控制可回收错误。

一些函数和方法不能总保证能够执行所有代码或产生有用的输出。可空类型用来表示值可能为空,但是当函数执行失败的时候,可空通常可以用来确定执行失败的原因,因此代码可以正确地响应失败。

举个例子,考虑到一个从磁盘上的一个文件读取以及处理数据的任务,有几种情况可能会导致这个任务失败,包括指定路径的文件不存在,文件不具有可读属性,或者文件没有被编码成合适的格式。区分这些错误可以让程序解决并且修复这些错误,并且,如果可能的话,把这些错误报告给用户。

注意:Swift中的错误处理涉及到错误处理样式,这会用到Cocoa中的NSError和Objective-C。

2、表示并且抛出错误

在Swift中,错误用符合ErrorType协议的值表示。

Swift枚举特别适合为一系列相关的错误建模,把一些表征错误本质的值关联在一起。

比如说,你可以这样表示操作自动贩卖机会出现的错误:

  1. enum VendingMachineError: ErrorType {
  2. case InvalidSelection
  3. case InsufficientFunds(coinsNeeded: Int)
  4. case OutOfStock
  5. }

在这种情况下,自动贩卖机可能会因为以下原因失败: 请求的物品不存在,用InvalidSelection表示。 请求的物品的价格高于已投入金额,用InsufficientFunds表示。相关的双精度值表示还需要多少钱来完成此次交易。 请求的物品已经卖完了,用OutOfStock表示。

错误抛出,可以让你知道异常发生并且正常的流程不能继续执行。通过在函数或方法声明的参数后面加上throws关键字,表明这个函数或方法可以抛出错误。例如自动贩卖机还需要5个硬币:

  1. throw VendingMachineError.InsufficientFunds(coinsNeeded: )

3、处理错误

当错误被抛出时,周围的代码环境必须处理这个错误,例如纠正问题、尝试另一种方法或者将错误信息报告给用户。

Swift中有四种方式来处理错误。你可以将错误从函数中传递给调用这个函数的代码,又或者用do-catch语句来处理这个错误,又或者当作一个可选值来处理这个错误,或者断言这个错误不会发生。下面将一一介绍这四种方式。

当函数抛出错误后,它就会改变你的程序的流向,所以快速判断抛出错误的位置很重要。因此,在你调用可能抛出错误的函数、方法或者构造函数的前面,加上try关键字(或者try?、try!)。

注意:Swift的错误处理与其他语言类似,用 trycatch 和 throw等关键字。和很多语言(包括Objective-C)中的异常处理不一样,Swift中的处理不会展开调用堆(性能损耗很大的一个过程)。因此,throw语句的性能和return语句差不多。

(1)通过函数抛出错误

为了表示一个函数、方法或构造器可能抛出错误,在函数的参数后面添加throw关键字。

如果函数指定一个返回值,则把throws关键字放在返回箭头(->)的前面。

  1. func canThrowErrors() throws -> String
  2.  
  3. func cannotThrowErrors() -> String

一个抛出函数(throwing function)在它被调用的地方传递它的函数体内抛出的错误。

注意:只有抛出函数能传递错误,非抛出函数内的错误必须被处理。

在下面的例子中,如果请求的物品不存在,或者卖完了,或者超出投入金额,vend(itemNamed:)函数会抛出一个错误:

  1. struct Item {
  2. var price: Int
  3. var count: Int
  4. }
  5.  
  6. class VendingMachine {
  7. var inventory = [
  8. "Candy Bar": Item(price: , count: ),
  9. "Chips": Item(price: , count: ),
  10. "Pretzels": Item(price: , count: )
  11. ]
  12. var coinsDeposited =
  13. func dispenseSnack(snack: String) {
  14. print("Dispensing \(snack)")
  15. }
  16.  
  17. func vend(itemNamed name: String) throws {
  18. guard var item = inventory[name] else {
  19. throw VendingMachineError.InvalidSelection
  20. }
  21.  
  22. guard item.count > else {
  23. throw VendingMachineError.OutOfStock
  24. }
  25.  
  26. guard item.price <= coinsDeposited else {
  27. throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
  28. }
  29.  
  30. coinsDeposited -= item.price
  31. --item.count
  32. inventory[name] = item
  33. dispenseSnack(name)
  34. }
  35. }

首先,用guard语句来检测条件,如果条件不满足,则抛出错误并推出函数。因为throw语句会马上改变程序流程,当所有的购买条件(物品存在,库存足够以及投入金额足够)都满足的时候,物品才会出售。

当调用一个抛出函数的时候,在调用前面加上try。这个关键字表明函数可以抛出错误,而且在try后面代码将不会执行。

由于vend(itemNamed:)函数传递了抛出的错误,所以,你在代码中调用这个方法时,必须直接处理错误——用do-catch语句, try?,或者 try!,或者继续传递错误。

例如下例中的buyFavoriteSnack(_:vendingMachine:)函数也是一个抛出函数, vend(itemNamed:)函数抛出的错误将会在传递到buyFavoriteSnack(_:vendingMachine:)被调用的地方。

  1. let favoriteSnacks = [
  2. "Alice": "Chips",
  3. "Bob": "Licorice",
  4. "Eve": "Pretzels",
  5. ]
  6. func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
  7. let snackName = favoriteSnacks[person] ?? "Candy Bar"
  8. try vendingMachine.vend(itemNamed: snackName)
  9. }

buyFavoriteSnack(_:vendingMachine:)函数根据人名查询最喜爱的物品,并调用vend(itemNamed:)尝试购买。由于vend(itemNamed:)可能抛出一个错误,调用前加try关键字。

(2)用do-catch语句处理错误

可以用do-catch执行代码块来处理错误。如果do代码块中的代码抛出了错误,并且和catch子句中的错误列表匹配,那么这个错误就可以处理。

do-catch语句的格式一个catch分句包含一个catch关键字,跟着一个pattern来匹配错误和相应的执行语句。:

  1. do {
  2. try expression
  3. statements
  4. } catch pattern {
  5. statements
  6. } catch pattern where condition {
  7. statements
  8. }

为了保证错误被处理,用一个带patterncatch分句来匹配错误。如果一个catch分句没有指定样式,这个分句会匹配并且绑定任何错误到一个本地error常量。

catch子句不必处理do子句中可能抛出的所有错误。如果一个错误没有被任意catch子句处理,这个错误将被传递到外部域。无论如何,这个错误必须在某个外部域中被处理,也许用一个封闭的do-catch子句处理,或者传递到一个抛出函数里。

  1. var vendingMachine = VendingMachine()
  2. vendingMachine.coinsDeposited =
  3. do {
  4. try buyFavoriteSnack("Alice", vendingMachine: vendingMachine)
  5. } catch VendingMachineError.InvalidSelection {
  6. print("Invalid Selection.")
  7. } catch VendingMachineError.OutOfStock {
  8. print("Out of Stock.")
  9. } catch VendingMachineError.InsufficientFunds(let coinsNeeded) {
  10. print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
  11. }
  12. // prints "Insufficient funds. Please insert an additional 2 coins."

在上面的例子中,vend(itemNamed:) 函数在try表达式中被调用,因为这个函数会抛出错误。如果抛出了错误,程序执行流程马上转到catch分句,在catch分句中确定错误传递是否继续传送。如果没有抛出错误,将会执行在do语句中剩余的语句。

(3)把错误转化成可选值

可以用try?来处理错误,这种方式把错误转化成可选值。如果一个错误在try?表达式中被抛出,这个表达式的值将为nil。

  1. func someThrowingFunction() throws -> Int {
  2. // ...
  3. }
  4.  
  5. let x = try? someThrowingFunction()
  6.  
  7. let y: Int?
  8. do {
  9. y = try someThrowingFunction()
  10. } catch {
  11. y = nil
  12. }

如果someThrowingFunction()抛出错误,则x或y的值为nil。否则,x或y的值等于函数的返回值。注意,x和y的类型都是函数的返回类型的可选型。

当你在想用同一种方式处理所有的错误的时候,try?可以让你方便的写出精准的错误处理方法。

  1. func fetchData() -> Data? {
  2. if let data = try? fetchDataFromDisk() { return data }
  3. if let data = try? fetchDataFromServer() { return data }
  4. return nil
  5. }

(4)禁止错误传递

有时候,你清楚某个抛出函数实际上在运行时不会抛出错误。这种情况下,可以在要禁止错误传递的表达式前面加上try!关键字,并且把这个调用包装在一个断言里,判断是否没有任何错误抛出。如果抛出了错误,将产生一个运行时错误。

下面的例子,通过指定的路径加载图片资源,如果图片无法加载,则抛出错误。实际上,这个图片保存在应用程序的资源中,运行时不会产生错误,所以可以禁止错误的传递。

  1. let photo = try! loadImage("./Resources/John Appleseed.jpg")

(5)指定收尾操作

在当前的代码块运行结束前,用defer语句执行一系列语句。无论代码块是如何结束的(无论是抛出错误,还是return、break),defer语句都可以执行一些必要的动作。例如,可以用defer语句来保证文件描述符被关闭并且手动释放了内存。

defer语句把执行推迟到要退出当前域的时候。defer语句包括defer关键字以及后面要执行的语句。被推迟的语句可能不包含任何将执行流程转移到外部的代码,比如break或者return语句,或者通过抛出一个错误。被推迟的操作的执行顺序和他们定义的顺序相反,也就是说,在第一个defer语句中的代码在第二个defer语句中的代码之后执行。

  1. func processFile(filename: String) throws {
  2. if exists(filename) {
  3. let file = open(filename)
  4. defer {
  5. close(file)
  6. }
  7. while let line = try file.readline() {
  8. // Work with the file.
  9. }
  10. // close(file) is called here, at the end of the scope.
  11. }
  12. }

上面的例子用defer语句来确保open(_:)函数一定调用对应的close(_:)函数。

注意:即使没有错误处理的代码,你也可以使用defer语句。

Swift2.1 语法指南——错误处理的更多相关文章

  1. Swift2.1 语法指南——访问控制

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  2. Swift2.1 语法指南——泛型

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  3. Swift2.1 语法指南——协议

    原档: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programm ...

  4. Swift2.1 语法指南——高级操作符

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  5. Swift2.1 语法指南——扩展

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  6. Swift2.1 语法指南——嵌套类型

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  7. Swift2.1 语法指南——类型转换

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  8. Swift2.1 语法指南——可空链式调用

    原档:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programmi ...

  9. Swift2.1 语法指南——自动引用计数

    原档: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programm ...

随机推荐

  1. C#调用c++的dll报错:“尝试读取或写入受保护的内存。这通常指示其他内存已损坏“

    一:c++代码内部报错引起.可能是空指针或者其他. 二:需要从c#代码调试进入c++代码.可以吧c++的dll和pdb拷入工程项目的debug目录下面. 三:我发现的错误时在C++内部声明啦全局变量, ...

  2. HTML5学习总结-09 拖放和手机触屏事件

    一 拖放 拖放(Drag 和 drop)是 HTML5 标准的组成部分.拖放是一种常见的特性,即抓取对象以后拖到另一个位置.在 HTML5 中,拖放是标准的一部分,任何元素都能够拖放. 课程参考 ht ...

  3. Linux安装配置sun-java

    一(不推荐) 1. 下载源码与解压 将下载的源码包,移动到/opt目录下: $ sudo mv ~/Downloads/jdk-8u65-linux-x64.tar.gz  /opt/ 解压: $ s ...

  4. IOS中在自定义控件(非视图控制器)的视图跳转中 代理方法与代码块的比较

    //代码块与代替代理的设计方法 我就以在自定义视图中(非视图控制器,不能实现视图控制功能),通过代理和代码块两种方法分别实现视图的跳转,进行对比 首先自定义了一个视图,上面有一个已经注册了得BUtto ...

  5. wpf arcglobe +c# 三维缩放到图层

    /// <summary>        /// 地图缩放到图层        /// </summary>        /// <param name="s ...

  6. BigInteger类

    当一个数字非常大时,则肯定无法使用基本类型接受,所以使用了BigInteger类. BigInteger类表示是大整数类,定义在java.math包中,如果在操作时一个整型数据已经超过了整数的最大类型 ...

  7. JavaWeb学习笔记——Tomcat相关

    Tomcat目录分析 1.bin 存放启动和关闭Tomcat的脚本文件 2.conf  存放Tomcat服务器的各种配置文件 3.lib  存放Tomcat服务器的支持jar包 4.logs  存放T ...

  8. B1/B2签证拒签

    http://www.mcdvisa.com/html/News/USA_visa_news/201529/152917GE.html

  9. 单点登录(SSO)系统的总结

    前些天一位其他开发部门的同事找到我们了解一些关于SSO单点登录的事,他们要做单点登录,同时也需要和我们这边的系统做集成,要我帮忙做一单点登录,了解关于单点登录的解决方案和资料,虽然做单点登录已经很久了 ...

  10. css媒体查询

    简单解释:http://zh.learnlayout.com/media-queries.html 深入学习1:https://developer.mozilla.org/en-US/docs/Web ...