从这篇文章,你将学习到 Swift 的错误处理,如何抛出、捕获和传播错误,让我们开始吧。

Swift Error Handling

表示和抛出错误

任何满足 Error 协议的值类型都可以表示错误,Swift 枚举通过关联值很适合表示一组相关的错误:

enum VendingMachineError: Error {
  case invalidSelection
  case insufficientFunds(coinsNeeded: Int)
  case outOfStock
}

使用 throw 来抛出错误:

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

处理错误

先说一下,表明一个函数、方法或初始化器可以抛出错误,在定义时要使用 throws 关键词:

func canThrowErrors() throws -> String

下面通过自动售货机售货时会抛出几种错误的示例,分别说明下处理错误的 4 种方法:

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 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)")
  }
}

传播错误

let favoriteSnacks = [
  "Alice": "Chips",
  "Bob": "Licorice",
  "Eve": "Pretzels"
]

func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
  let snackName = favoriteSnacks[person] ?? "Candy Bar"
  try vendingMachine.vend(itemNamed: snackName)
}

捕获错误

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8

do {
  try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
} catch VendingMachineError.invalidSelection {
  print("Invalid Selection.")
} catch VendingMachineError.outOfStock {
  print("Out of Stock.")
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
  print("Insufficient funds. Please insert an additional \(coinsNeeded) coins.")
}

转换错误为可选类型值

func someThrowingFunction() throws -> Int {
}

let x = try? someThrowingFunction()

禁止传播错误

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

指定一些清理操作

defer 可以表明一些在离开当前代码块必须执行的代码:

func processFile(filename: String) throws {
  if exists(filename) {
    let file = open(filename)
    defer {
      close(file)
    }
    while let line = try file.readline() {
      // 操作文件内容
    }
    // close(file) 在这里被调用
  }
}