Swift错误处理

可能发生的错误分为两大类:可恢复的错误和不可恢复的错误。

常见的可恢复错误:

  • 试图打开不存在的文件
  • 试图和下线的服务器通信
  • 试图在设备没有网络连接时通信

常见的不可恢复错误:

  • 强制展开值为nil的可空实例
  • 数组越界

说明:Swift中的错误处理涉及到错误处理模式,这会用到Cocoa和OC中NSError。

表示并抛出错误

在 Swift 中,错误用符合Error协议的类型的值来表示。这个空协议表明该类型可以用于错误处理。

  • 枚举类型尤为适合构建一组相关的错误状态,枚举的关联值还可以提供错误状态的额外信息。
  • 抛出错误用throw关键字

示例:

1
2
3
4
5
6
enum VendingMachineError: Error {
case invalidSelection //选择无效
case insufficientFunds(coinsNeeded: Int) //金额不足
case outOfStock //缺货
}
throw VendingMachineError. insufficientFunds(coinsNeeded: 5)

处理错误方式

  1. throwing函数传递错误

    • 声明的参数列表后加thorws关键字的函数被称为throwing函数,表示这种函数可以抛出错误。如果函数有返回值,throws需要写在->前。
    • 一个 throwing 函数可以在其内部抛出错误,并将错误传递到函数被调用时的作用域。
    • 只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    struct Item {
    var price: Int
    var count: Int
    }

    class VendingMachine {
    var inventory = [
    "Candy Bar": Item(price: 12, count: 7),
    "Chips": Item(price: 10, count: 4),
    "Pretzels": Item(price: 7, count: 11)
    ]
    var coinsDeposited = 0
    func dispenseSnack(snack: String) {
    print("Dispensing \(snack)")
    }

    func vend(itemNamed name: String) throws {
    guard let item = inventory[name] else {
    throw VendingMachineError.InvalidSelection
    }

    guard item.count > 0 else {
    throw VendingMachineError.OutOfStock
    }

    guard item.price <= coinsDeposited else {
    throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
    }

    coinsDeposited -= item.price

    var newItem = item
    newItem.count -= 1
    inventory[name] = newItem

    print("Dispensing \(name)")
    }
    }
  2. do-catch处理错误

    do-catch语句一般形式:

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

    catch后面跟一个匹配模式来表明这个句子能处理什么样的错误。如果一条catch子句没有指定匹配模式,那么这条子句可以匹配任何错误,并且把错误绑定到一个名字为error的局部常量。

    catch子句不必将do子句中的代码所抛出的每一个可能的错误都做处理。如果所有catch子句都未处理错误,错误就会传递到周围的作用域。

    在do中至少有一个try语句。

  3. 将错误转换成可选值

    可以使用try?通过将错误转换成一个可选值来处理错误。如果在评估try?表达式时一个错误被抛出,那么表达式的值就是nil

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

    有时你知道某个throwing函数实际上在运行时是不会抛出错误的,在这种情况下,你可以在表达式前面写try!来禁用错误传递,这会把调用包装在一个不会有错误抛出的运行时断言中。如果真的抛出了错误,你会得到一个运行时错误。

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

指定清理操作

可以使用defer语句在即将离开当前代码块时执行一系列语句。该语句让你能执行一些必要的清理工作,不管是以何种方式离开当前代码块的——无论是由于抛出错误而离开,还是由于诸如return或者break的语句。例如,你可以用defer语句来确保文件描述符得以关闭,以及手动分配的内存得以释放。

defer语句将代码的执行延迟到当前的作用域退出之前。该语句由defer关键字和要被延迟执行的语句组成。延迟执行的语句不能包含任何控制转移语句,例如break或是return语句,或是抛出一个错误。延迟执行的操作会按照它们被指定时的顺序的相反顺序执行——也就是说,第一条defer语句中的代码会在第二条defer语句中的代码被执行之后才执行,以此类推。

即使没有涉及到错误处理,你也可以使用defer语句。

1
2
3
4
5
6
7
8
9
10
11
12
func processFile(filename: String) throws {
if exists(filename) {
let file = open(filename)
defer {
close(file)
}
while let line = try file.readline() {
// 处理文件。
}
// close(file) 会在这里被调用,即作用域的最后。
}
}