176

我尝试理解 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 可以理解在处理每个案例时它是详尽的。

4

6 回答 6

294

Swift 2 错误处理模型有两点很重要:穷举性和弹性。总之,它们归结为您的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")
}

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

于 2015-06-08T23:44:10.197 回答
30

我怀疑这还没有正确实施。Swift Programming Guide似乎暗示编译器可以“像 switch 语句”那样推断出详尽的匹配。它没有提到需要一个将军catch才能详尽无遗。

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

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

更新:

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

Beta 4 更新:

Xcode 7b4 添加了对 doc 注释的支持Throws:,“应该用来记录可以抛出哪些错误以及为什么会抛出错误”。我想这至少提供了一些将错误传达给 API 使用者的机制。当您有文档时,谁需要类型系统!

另一个更新:

在花了一些时间希望自动ErrorType推理并弄清楚该模型的局限性之后,我改变了主意 -就是我希望 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 的错误处理原理现在可在此处获得。在swift-evolution邮件列表上也有一些有趣的讨论。从本质上讲,John McCall 反对类型错误,因为他认为大多数库最终都会包含一个通用错误案例,并且类型错误不太可能在样板代码之外添加太多代码(他使用了“抱负虚张声势”这个词)。Chris Lattner 表示,如果 Swift 3 可以与弹性模型一起使用,他对输入错误持开放态度。

于 2015-06-12T10:16:34.883 回答
3

Swift 担心您的 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")
}
于 2015-06-08T23:19:13.687 回答
3

我也对缺少函数可以抛出的类型感到失望,但是感谢@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 的函数的任何其他地方

于 2015-06-13T07:22:23.007 回答
1
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)
  }
于 2019-05-20T10:07:17.467 回答
-2

像这样创建枚举:

//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...")
}
于 2018-03-08T09:32:27.287 回答