Swift捕获模式:将错误绑定到变量上

17

使用Swift 4.2和XCode 10

在Swift 4.2中,DecodingError是一个枚举类型。目前有四种不同的情况。我可以单独捕获每种情况,并绑定变量,以便我可以像下面的代码一样记录错误...

do {
    let model = try jsonDecoder.decode(BattleShip.self, from: jsonData!)
    print(model)
} catch DecodingError.dataCorrupted(let context) {
    print(context.debugDescription)
} catch DecodingError.keyNotFound(let key, let context) {
    print("\(key.stringValue) was not found, \(context.debugDescription)")
} catch DecodingError.typeMismatch(let type, let context) {
    print("\(type) was expected, \(context.debugDescription)")
} catch DecodingError.valueNotFound(let type, let context) {
    print("no value was found for \(type), \(context.debugDescription)")
} catch {
    print("I know not this error")
}

但是这样的代码量很大,需要在可能遇到解码错误的每个地方都加上。而且,如果我的do {}块有多个调用会抛出异常,我可能需要以不同的方式处理那些方法调用引起的错误。我尝试实现的模式看起来像这样...其中decodingError(error)包含以上所有混乱的代码。

do {
    let object1 = try decoder.decode(SomeClass.self, from: someData)
    try object2.methodThatThrowsSomeOtherError()
} catch <all decoding errors> {      // this is invalid pseudocode
    MyCentralLogger.log.decodingError(error)
} catch let nonDecodingError {
    MyCentralLogger.log.error(nonDecodingError)
}

我可以有一个类似这样的捕获模式,它似乎满足所有枚举情况(至少可以编译)

} catch is DecodingError {

但编译器似乎不会自动绑定 'error' 变量,并且我没有看到任何选项,例如

} catch let decodingError is DecodingError {  // THIS IS NOT VALID

如果我只是捕捉所有错误,那么我可以轻松地在一个中央方法中加入switch语句,以适当地分离不同的解码错误情况。但我希望能够避免将非解码错误发送到该开关。我也可以将我的do {}块分开,这样我只需要执行解码步骤,但这也会使代码变得混乱,特别是如果您正在解码多个消息并穿插其他操作。

有什么建议吗?谢谢!

3个回答

35
catch语句中使用的语法与switchcase使用的模式语法完全相同。如果您知道如何编写case,则知道如何编写catch。所以举个例子,你抱怨:
} catch let decodingError is DecodingError {  // THIS IS NOT VALID

没错。但是这个确实是有效的:

} catch let decodingError as DecodingError { 

哦,一个字母能带来如此大的不同。


谢谢Matt。不幸的是,我认为你的解决方案只揭示了我的另一个错误/假设。当我使用'as'时,编译器现在抱怨我的catch块不够全面。它不允许DecodingError代表其所有枚举。 :( - escapedcanadian
它确实代表了其所有枚举情况;问题在于你可能会抛出其他错误。就像开关一样,情况必须是穷尽的。这就是为什么你仍然需要在一系列catch之后使用catchcatch let nonDecodingError。你总是需要一个最终的清理catch!然而,这与问题无关;我回答了你所问的问题。 - matt
当然,你做到了,非常感谢。当我在一个没有在顶部声明throws的方法中尝试你的解决方案时,我把自己搞糊涂了。我的其他情况都有这个声明,所以catch块不需要是详尽无遗的。再次感谢。 - escapedcanadian
1
规则是,do...catch 结构通常需要是全面的——也就是说,在结尾处需要一个清理 catch。只有在 throws 函数内部,它不需要是全面的,因为在 do 块中抛出但未被任何 catch 块捕获的错误可以传递到函数外部,以便在调用链中更高的位置捕获。 - matt

2
这仍然比期望的代码量多,但可能更加简洁一些:
} catch DecodingError.keyNotFound(_, let context),
        DecodingError.valueNotFound(_, let context),
        DecodingError.typeMismatch(_, let context),
        DecodingError.dataCorrupted(let context) {
    print(context.debugDescription)
    MyCentralLogger.log.decodingError(context.underlyingError)
} catch {
    print(error.localizedDescription)
    MyCentralLogger.log.error(error)
}

0

结合有效的答案和switch语句,以下代码可以帮助你节省一些几乎相同的行:

do {
    let model = try jsonDecoder.decode(BattleShip.self, from: jsonData!)
    print(model)
} catch let decodingError as DecodingError {
    switch decodingError {
    case .typeMismatch(_, let c), .valueNotFound(_, let c), .keyNotFound(_, let c), .dataCorrupted(let c):
        print(c.debugDescription)
    }
} catch {
    print(error.debugDescription)
}

如果您的解码数据缺少属性index: Int,它将打印以下内容:

没有与键CodingKeys(stringValue: "index", intValue: nil) ("index")相关联的值。

这应该足够清晰地用于调试目的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接