Swift从入门到精通第十四篇 - 错误处理 初识

错误处理(学习笔记)

环境Xcode 11.0 beta4 swift 5.1

  • 错误表现和抛出
    • swift 中,错误由符合 Error 协议的类型值表示

      // 示例
      enum VendingMachineError: Error {
          case invalidSelection
          case insufficientFunds(coinsNeeded: Int)
          case outOfStock
      }
      // 抛出错误
      throw VendingMachineError.insufficientFunds(coinsNeeded: 5)
  • 错误处理
    • 在 Swift 中有四种处理错误的方式:一、从函数中把错误传递出来;二、用 do-catch 语句;三、作为可选值处理;四、用断言
    • 在 Swift 中错误处理与其它语言(包括OC)的异常类似,Swift 中错误处理不会涉及展开堆栈的调用,而堆栈的调用消耗是很大的,从性能的角度出发 throw 语句的性能与 return 性能是一样的
    • 函数抛出错误,只有声明了 throws 的函数才能往外抛出错误,如果不是则抛出的错误必须在函数内部处理

      func canThrowErrors() throws -> String
      // 
      func cannotThrowErrors() -> String
    • 示例代码

      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) // 如果这里有错误,将会向外层抛
      }
    • 可抛出的初始化器也可以向外抛出错误

      struct PurchasedSnack {
          let name: String
          init(name: String, vendingMachine: VendingMachine) throws {
              try vendingMachine.vend(itemNamed: name)
              self.name = name
          }
      }
    • do-catch 处理错误,一般格式如下

      do {
          try expression
          // statements
      } catch pattern1 {
          // statements
      } catch pattern2 where condition {
          // statements
      } catch {
          // statements
      }
      // catch 后的 pattern1 表示这个大括号内处理 pattern1 类型的错误,如果省略不写表示处理任何类型的错误且把错误绑定到一个常量 `error`
      var vendingMachine = VendingMachine()
      vendingMachine.coinsDeposited = 8
      do {
          try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
          print("Success! Yum.")
      } 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.")
      } catch {
          print("Unexpected error: \(error).")
      }
      // Prints "Insufficient funds. Please insert an additional 2 coins."
    • 将错误转换为可选值
      • 可以用 try? 将错误转换为可选值,如果有错误抛出,计算 try? 表达式的值为 nil

        swift func someThrowingFunction() throws -> Int { // .... } let x = try? someThrowingFunction() let y: Int? do { y = try someThrowingFunction() }catch { y = nil } // 如果函数 someThrowingFunction 抛出错误,则 x 的值为 nil;否则 x、y 就是函数返回的值,且此时x、y是可选值

      • 如果想用 try? 准确处理所有错误,如下示例

        swift func fetchData() -> Data? { if let data = try? fecthDataFromDisk() { return data } if let data = try? fetchDataFromDisk() { return data } return nil }

    • 禁止错误传递,可以用 try! , 如果值是 nil,则会抛出运行时错误
    // 示例
    let photo = try! loadImage(atPath: "./Resources/John Appleseed.jpg")
  • 指定清理行为
    • 在当前代码块结束之前,可以用 defer 语句执行一组语句,做任何必需的清理操作,语句的执行无关是抛出错误或者是 returnbreak 关键字之类
    • defer 语句源代码的顺序与执行的顺序相反,即前面的后执行,后面的先执行

      func processFile(filename: String) throws {
          if exists(filename) {
              let file = open(filename)
              defer {
                  close(file)
              }
              while let line = try file.readline() {
                  // Work with the file.
              }
              // close(file) is called here, at the end of the scope.
          }
      }