Swift do-try-catch 语法

Swift do-try-catch syntax

我试着理解 swift 中新的错误处理方式 2. 这是我所做的:我首先声明了一个错误枚举:

enum SandwichError: ErrorType {
    case NotMe
    case DoItYourself
}

然后我声明了一个抛出错误的方法(伙计们,这不是异常。这是一个错误。)。这是该方法:

func makeMeSandwich(names: [String: String]) throws -> String {
    guard let sandwich = names["sandwich"] else {
        throw SandwichError.NotMe
    }

    return sandwich
}

问题出在调用方。下面是调用此方法的代码:

let kitchen = ["sandwich": "ready", "breakfeast": "not ready"]

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
}

do 行之后,编译器说 Errors thrown from here are not handled because the enclosing catch is not exhaustive。但在我看来它是详尽无遗的,因为 SandwichError 枚举中只有两种情况。

对于常规的 switch 语句 swift 可以理解它在处理每种情况时都是详尽无遗的。

Swift 担心您的 case 语句没有涵盖所有情况,要修复它您需要创建一个默认 case:

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch Default {
    print("Another Error")
}

Swift2 错误处理模型有两个要点:详尽性和弹性。它们一起归结为您的 do/catch 语句需要捕获所有可能的错误,而不仅仅是您知道可以抛出的错误。

请注意,您没有声明函数可以抛出什么类型的错误,只声明它是否抛出。这是一个零一无穷大的问题:作为为其他人(包括你未来的自己)定义一个函数的人,你不想让你的函数的每个客户适应你的实现中的每一个变化函数,包括它可以抛出的错误。您希望调用函数的代码能够适应这种变化。

因为你的函数不能说明它抛出什么类型的错误(或者将来可能抛出),捕获它错误的 catch 块不知道它可能抛出什么类型的错误。因此,除了处理您知道的错误类型外,您还需要使用通用 catch 语句处理您不知道的错误类型——这样,如果您的函数更改了它在未来抛出的错误集,调用者仍然会捕获它的错误。

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch SandwichError.NotMe {
    print("Not me error")
} catch SandwichError.DoItYourself {
    print("do it error")
} catch let error {
    print(error.localizedDescription)
}

但我们不止于此。多想想这个弹性的想法。按照你设计三明治的方式,你必须在你使用它们的每个地方描述错误。这意味着无论何时更改错误案例集,都必须更改使用它们的每个地方......不是很有趣。

定义你自己的错误类型背后的想法是让你集中这样的事情。您可以为错误定义一个 description 方法:

extension SandwichError: CustomStringConvertible {
    var description: String {
        switch self {
            case NotMe: return "Not me error"
            case DoItYourself: return "Try sudo"
        }
    }
}

然后你的错误处理代码可以要求你的错误类型来描述它自己——现在你处理错误的每个地方都可以使用相同的代码,并且也可以处理未来可能出现的错误情况。

do {
    let sandwich = try makeMeSandwich(kitchen)
    print("i eat it \(sandwich)")
} catch let error as SandwichError {
    print(error.description)
} catch {
    print("i dunno")
}

这也为错误类型(或它们的扩展)支持其他报告错误的方式铺平了道路——例如,您可以在错误类型上有一个知道如何呈现 UIAlertController 用于向 iOS 用户报告错误。

我怀疑这还没有正确实施。 Swift Programming Guide 似乎绝对意味着编译器可以推断出详尽的匹配项 'like a switch statement'。它没有提到需要一个通用的 catch 才能详尽无遗。

您还会注意到错误在 try 行,而不是块的末尾,即在某些时候,编译器将能够查明哪个 try 语句在块具有未处理的异常类型。

虽然文档有点模棱两可。我浏览了“Swift 中的新功能”视频,但找不到任何线索;我会继续努力的。

更新:

我们现在已经到了 Beta 3,没有任何 ErrorType 推断的提示。我现在相信,如果这是曾经计划过的(我仍然认为它在某个时候是这样),协议扩展的动态调度可能会扼杀它。

Beta 4 更新:

Xcode 7b4 添加了对 Throws: 的文档注释支持,“应该用于记录可以抛出的错误以及原因”。我想这至少提供了 一些 机制来将错误传达给 API 消费者。有了文档,谁还需要类型系统!

另一个更新:

在花了一些时间希望自动 ErrorType 推理并弄清楚该模型的局限性之后,我改变了主意 - this 是我希望 Apple 实现的.本质上:

// allow us to do this:
func myFunction() throws -> Int

// or this:
func myFunction() throws CustomError -> Int

// but not this:
func myFunction() throws CustomErrorOne, CustomErrorTwo -> Int

又一个更新

Apple 的错误处理原理现在可用 here. There have also been some interesting discussions on the swift-evolution 邮件列表。本质上,John McCall 反对类型化错误,因为他相信大多数库最终都会包含一般性错误情况,并且类型化错误不太可能在样板代码之外增加太多代码(他使用术语 'aspirational bluff') . Chris Lattner 表示,如果 Swift3 可以与弹性模型一起使用,他愿意接受输入错误。

我也对缺少函数可以抛出的类型感到失望,但由于@rickster,我现在明白了,我将这样总结:假设我们可以指定函数抛出的类型,我们会有这样的东西:

enum MyError: ErrorType { case ErrorA, ErrorB }

func myFunctionThatThrows() throws MyError { ...throw .ErrorA...throw .ErrorB... }

do {
    try myFunctionThatThrows()
}
case .ErrorA { ... }
case .ErrorB { ... }

问题是,即使我们不更改 myFunctionThatThrows 中的任何内容,如果我们只是向 MyError 添加一个错误案例:

enum MyError: ErrorType { case ErrorA, ErrorB, ErrorC }

我们被搞砸了,因为我们的 do/try/catch 不再详尽无遗,以及我们调用抛出 MyError

的函数的任何其他地方

像这样创建枚举:

//Error Handling in swift
enum spendingError : Error{
case minus
case limit
}

创建方法如下:

 func calculateSpending(morningSpending:Double,eveningSpending:Double) throws ->Double{
if morningSpending < 0 || eveningSpending < 0{
    throw spendingError.minus
}
if (morningSpending + eveningSpending) > 100{
    throw spendingError.limit
}
return morningSpending + eveningSpending
}

现在检查是否有错误并处理:

do{
try calculateSpending(morningSpending: 60, eveningSpending: 50)
} catch spendingError.minus{
print("This is not possible...")
} catch spendingError.limit{
print("Limit reached...")
}
enum NumberError: Error {
  case NegativeNumber(number: Int)
  case ZeroNumber
  case OddNumber(number: Int)
}

extension NumberError: CustomStringConvertible {
         var description: String {
         switch self {
             case .NegativeNumber(let number):
                 return "Negative number \(number) is Passed."
             case .OddNumber(let number):
                return "Odd number \(number) is Passed."
             case .ZeroNumber:
                return "Zero is Passed."
      }
   }
}

 func validateEvenNumber(_ number: Int) throws ->Int {
     if number == 0 {
        throw NumberError.ZeroNumber
     } else if number < 0 {
        throw NumberError.NegativeNumber(number: number)
     } else if number % 2 == 1 {
         throw NumberError.OddNumber(number: number)
     }
    return number
}

现在验证号码:

 do {
     let number = try validateEvenNumber(0)
     print("Valid Even Number: \(number)")
  } catch let error as NumberError {
     print(error.description)
  }