Swift5.3 语言指南(十九) 错误处理

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
➤微信公众号:山青咏芝(shanqingyongzhi)
➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/
➤GitHub地址:https://github.com/strengthen/LeetCode
➤原文地址:https://www.cnblogs.com/strengthen/p/9739443.html 
➤如果链接不是山青咏芝的博客园地址,则可能是爬取作者的文章。
➤原文已修改更新!强烈建议点击原文地址阅读!支持作者!支持原创!
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

错误处理是响应程序错误状态并从错误状态中恢复的过程。Swift在运行时为抛出,捕获,传播和操作可恢复错误提供了一流的支持。

某些操作不能保证总是完成执行或产生有用的输出。可选变量用于表示缺少值,但是当操作失败时,了解导致失败的原因通常很有用,以便您的代码可以做出相应的响应。

例如,考虑从磁盘上的文件读取和处理数据的任务。此任务可能有多种方式失败,包括指定路径中不存在的文件,没有读取权限的文件或未以兼容格式编码的文件。在这些不同情况之间进行区分可以使程序解决一些错误,并向用户传达它无法解决的任何错误。

注意

Swift中的错误处理与使用NSErrorCocoa和Objective-C中类的错误处理模式互操作有关此类的更多信息,请参见在Swift中处理可可错误

表示和抛出错误

在Swift中,错误由符合Error协议的类型的值表示此空协议表示可以将类型用于错误处理。

Swift枚举特别适合于对一组相关的错误条件进行建模,其关联值允许传达有关错误性质的其他信息。例如,这是在游戏内操作自动售货机的错误状态的表达方式:

  1. enum VendingMachineError: Error {
  2. case invalidSelection
  3. case insufficientFunds(coinsNeeded: Int)
  4. case outOfStock
  5. }

引发错误可让您指示发生了意外情况,并且正常的执行流程无法继续进行。您使用一条throw语句引发错误。例如,以下代码引发错误,以指示自动售货机需要另外五个硬币:

  1. throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

处理错误

引发错误时,周围的一些代码段必须负责处理错误-例如,通过更正问题,尝试其他方法或将故障通知用户。

Swift中有四种处理错误的方法。您可以将错误从函数传播到调用该函数的代码,使用docatch语句处理错误,将错误作为可选值处理,或者断言不会发生错误。每种方法在下面的部分中进行介绍。

当一个函数抛出错误时,它会改变程序的流程,因此,重要的是您必须快速识别代码中可能引发错误的位置。要在代码中标识这些位置,请在一段调用可能引发错误的函数,方法或初始化程序的代码之前,写上try关键字(或the try?try!variant)。这些关键字在以下各节中介绍。

注意

错误迅速处理类似的异常处理在其他语言中,使用了的trycatchthrow关键字。与许多语言(包括Objective-C)中的异常处理不同,Swift中的错误处理不涉及展开调用堆栈,该过程在计算上可能会非常昂贵。这样,throw语句的性能特征可与语句的性能特征相媲美return

使用投掷函数传播错误

为了表明函数,方法或初始化程序可能引发错误,请throws在函数的参数后的声明中写入关键字。throws有的函数称为抛出函数如果函数指定了返回类型,则throws在返回箭头(->之前写入关键字

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

抛出函数将抛出的错误传播到调用它的作用域中。

注意

只有throwing函数可以传播错误。抛出于非抛出函数内部的任何错误都必须在函数内部进行处理。

在下面的示例中,VendingMachine该类具有一个vend(itemNamed:)方法,VendingMachineError如果所请求的物料不可用,缺货或成本超过当前的存放量,则抛出一个适当方法

  1. struct Item {
  2. var price: Int
  3. var count: Int
  4. }
  5. class VendingMachine {
  6. var inventory = [
  7. "Candy Bar": Item(price: 12, count: 7),
  8. "Chips": Item(price: 10, count: 4),
  9. "Pretzels": Item(price: 7, count: 11)
  10. ]
  11. var coinsDeposited = 0
  12. func vend(itemNamed name: String) throws {
  13. guard let item = inventory[name] else {
  14. throw VendingMachineError.invalidSelection
  15. }
  16. guard item.count > 0 else {
  17. throw VendingMachineError.outOfStock
  18. }
  19. guard item.price <= coinsDeposited else {
  20. throw VendingMachineError.insufficientFunds(coinsNeeded: item.price - coinsDeposited)
  21. }
  22. coinsDeposited -= item.price
  23. var newItem = item
  24. newItem.count -= 1
  25. inventory[name] = newItem
  26. print("Dispensing (name)")
  27. }
  28. }

vend(itemNamed:)方法的实现使用guard语句来提前退出该方法,如果不满足购买零食的任何要求,则抛出适当的错误。因为一条throw语句会立即转移程序控制权,所以只有在满足所有这些要求的情况下,才可以出售商品。

因为该vend(itemNamed:)方法传播了它抛出的任何错误,所以任何调用此方法的代码都必须使用docatch语句try?,或try!处理错误或者继续传播它们。例如,buyFavoriteSnack(person:vendingMachine:)在下面的示例中,也是一个throwing函数,该vend(itemNamed:)方法引发的任何错误都将传播到buyFavoriteSnack(person: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(person: vendingMachine:)vend(itemNamed:)vend(itemNamed:)try

引发初始化器可以像引发函数一样传播错误。例如,PurchasedSnack下面清单中结构的初始化程序在初始化过程中调用了throwing函数,并通过将其传播给调用方来处理遇到的任何错误。

  1. struct PurchasedSnack {
  2. let name: String
  3. init(name: String, vendingMachine: VendingMachine) throws {
  4. try vendingMachine.vend(itemNamed: name)
  5. self.name = name
  6. }
  7. }

使用Do-Catch处理错误

您可以使用docatch语句通过运行代码块来处理错误。如果do子句中的代码引发错误,则将其与catch子句进行匹配以确定其中哪一个可以处理该错误。

这是docatch语句的一般形式

  1. do {
  2. try expression
  3. statements
  4. } catch pattern 1 {
  5. statements
  6. } catch pattern 2 where condition {
  7. statements
  8. } catch pattern 3, pattern 4 where condition {
  9. statements
  10. } catch {
  11. statements
  12. }

之后写一个模式catch来指示该子句可以处理的错误。如果catch子句没有模式,则该子句匹配任何错误,并将错误绑定到名为的局部常量error有关模式匹配的更多信息,请参见模式

例如,以下代码与VendingMachineError枚举的所有三种情况匹配

  1. var vendingMachine = VendingMachine()
  2. vendingMachine.coinsDeposited = 8
  3. do {
  4. try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
  5. print("Success! Yum.")
  6. } catch VendingMachineError.invalidSelection {
  7. print("Invalid Selection.")
  8. } catch VendingMachineError.outOfStock {
  9. print("Out of Stock.")
  10. } catch VendingMachineError.insufficientFunds(let coinsNeeded) {
  11. print("Insufficient funds. Please insert an additional (coinsNeeded) coins.")
  12. } catch {
  13. print("Unexpected error: (error).")
  14. }
  15. // Prints "Insufficient funds. Please insert an additional 2 coins."

在上面的示例中,该buyFavoriteSnack(person:vendingMachine:)函数在try表达式中被调用,因为它可能引发错误。如果抛出错误,执行将立即转移到catch子句,这些子句决定是否允许继续传播。如果没有匹配的模式,则该错误将被final catch子句捕获,并绑定到局部error常量。如果没有引发错误,do则执行语句中的其余语句

catch条款没有处理每一个可能的错误,该代码do子句可以抛出。如果没有任何一个catch子句处理该错误,则该错误会传播到周围的范围。然而,传播错误必须处理一些周边范围。在非抛出函数中,必须使用docatch语句处理该错误。在throwing函数中,封闭的docatch语句或调用者必须处理错误。如果错误未得到处理就传播到顶级范围,则会出现运行时错误。

例如,可以编写上面的示例,以便VendingMachineError调用函数捕获不是a的任何错误

  1. func nourish(with item: String) throws {
  2. do {
  3. try vendingMachine.vend(itemNamed: item)
  4. } catch is VendingMachineError {
  5. print("Couldn't buy that from the vending machine.")
  6. }
  7. }
  8. do {
  9. try nourish(with: "Beet-Flavored Chips")
  10. } catch {
  11. print("Unexpected non-vending-machine-related error: (error)")
  12. }
  13. // Prints "Couldn't buy that from the vending machine."

在该nourish(with:)函数中,如果vend(itemNamed:)引发VendingMachineError枚举情况之一nourish(with:)的错误,则通过打印消息来处理该错误。否则,nourish(with:)将错误传播到其呼叫站点。然后,该错误被通用catch子句捕获

捕获多个相关错误的另一种方法是将它们列出在后面catch,并用逗号分隔。例如:

  1. func eat(item: String) throws {
  2. do {
  3. try vendingMachine.vend(itemNamed: item)
  4. } catch VendingMachineError.invalidSelection, VendingMachineError.insufficientFunds, VendingMachineError.outOfStock {
  5. print("Invalid selection, out of stock, or not enough money.")
  6. }
  7. }

eat(item:)功能列出了要捕获的自动售货机错误,其错误文本与该列表中的项目相对应。如果抛出三个列出的错误中的任何一个,则此catch子句通过打印消息来处理它们。其他任何错误都会传播到周围的范围,包括以后可能会添加的自动售货机错误。

将错误转换为可选值

您可以try?通过将错误转换为可选值来处理错误。如果在评估try?表达式时抛出错误,则表达式的值为nil例如,在以下代码中xy它们具有相同的值和行为:

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

如果someThrowingFunction()抛出错误,则xand的y值为nil否则,xand y的值就是函数返回的值。请注意,xysomeThrowingFunction()返回的可选类型这里的函数返回一个整数,所以xy是可选的整数。

try?当您要以相同方式处理所有错误时,使用using 可以编写简洁的错误处理代码。例如,以下代码使用几种方法来获取数据,nil如果所有方法均失败,则返回

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

禁用错误传播

有时,您知道抛出函数或方法实际上不会在运行时抛出错误。在这种情况下,可以try!在表达式之前编写以禁用错误传播,并将调用包装在不会引发任何错误的运行时断言中。如果实际抛出错误,您将收到运行时错误。

例如,以下代码使用一个loadImage(atPath:)函数,该函数在给定的路径上加载图像资源,或者在无法加载图像时抛出错误。在这种情况下,由于该映像是随应用程序一起提供的,因此在运行时不会引发任何错误,因此应该禁用错误传播。

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

指定清理措施

您可以defer在代码执行离开当前代码块之前使用一条语句来执行一组语句。通过该语句,无论执行如何离开当前代码块,无论是由于引发错误还是由于诸如return之类的语句离开,您都可以执行该操作break例如,您可以使用一条defer语句来确保关闭文件描述符并释放手动分配的内存。

一条defer语句推迟执行,直到退出当前范围。该语句由defer关键字和以后要执行的语句组成延迟的语句可能不包含任何将控制权移出该语句的代码,例如a break或一条return语句,或引发错误。延迟的操作以与您在源代码中写入的顺序相反的顺序执行。也就是说,第一个defer语句中的代码最后执行,第二个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即使不涉及任何错误处理代码,也可以使用语句。

原文地址:https://www.cnblogs.com/strengthen/p/9739443.html