在 DecodingError 中采用 CustomNSError

2024-05-27

我正在使用 Crashlytics 编写一个错误记录器,并且遇到了一个问题,这个问题让我质疑我对协议和动态调度的理解。

使用 Crashlytics 记录非致命错误时,API 需要一个符合错误的对象和一个可选的用户信息字典。我现在正在查看 JSON 解码错误,当我刚刚在 recordError 中发送 DecodingError 时,我对 Crashlytics 仪表板中看到的内容不太满意。所以我的解决方案是采用 CustomNSError 为 DecodingError 编写一个扩展,以提供一些更详细的信息以帮助将来进行调试:

extension DecodingError: CustomNSError {

    public static var errorDomain: String {
        return "com.domain.App.ErrorDomain.DecodingError"
    }

    public var errorCode: Int {
        switch self {
        case .dataCorrupted:
            return 1
        case .keyNotFound:
            return 2
        case .typeMismatch:
            return 3
        case .valueNotFound:
            return 4
        }
    }

    public var errorUserInfo: [String : Any] {
        switch self {
        case .dataCorrupted(let context):
            var userInfo: [String: Any] = [
                "debugDescription": context.debugDescription,
                "codingPath": context.codingPath.map { $0.stringValue }.joined(separator: ".")
            ]

            guard let underlyingError = context.underlyingError else { return userInfo }

            userInfo["underlyingErrorLocalizedDescription"] = underlyingError.localizedDescription
            userInfo["underlyingErrorDebugDescription"] = (underlyingError as NSError).debugDescription

            userInfo["underlyingErrorUserInfo"] = (underlyingError as NSError).userInfo.map {
                return "\($0.key): \(String(describing: $0.value))"
            }.joined(separator: ", ")

            return userInfo
        case .keyNotFound(let codingKey, let context):
            return [
                "debugDescription": context.debugDescription,
                "codingPath": context.codingPath.map { $0.stringValue }.joined(separator: "."),
                "codingKey": codingKey.stringValue
            ]
        case .typeMismatch(_, let context), .valueNotFound(_, let context):
            return [
                "debugDescription": context.debugDescription,
                "codingPath": context.codingPath.map { $0.stringValue }.joined(separator: ".")
            ]
        }
    }
}

我在记录器中编写了一个方法,如下所示:

func log(_ error: CustomNSError) {
    Crashlytics.sharedInstance().recordError(error)
}

我将错误发送到这里:

do {

        let decoder = JSONDecoder()

        let test = try decoder.decode(SomeObject.self, from: someShitJSON)

    } catch(let error as DecodingError) {

        switch error {

        case .dataCorrupted(let context):

            ErrorLogger.sharedInstance.log(error)
        default:
            break
    }
}

但是传递给 log(_ error:) 的对象不是我的 CustomNSError 实现,看起来像带有 NSCocoaErrorDomain 的标准 NSError。

我希望这足够详细来解释我的意思,不确定为什么传递给 log 的对象没有我在 DecodingError 扩展中设置的值。我知道我可以轻松地在调用 Crashlytics 时单独发送额外的用户信息,但我很想知道我对这种情况的理解哪里出了问题。


NSError桥接是 Swift 编译器中一个有趣的野兽。一方面,NSError来自 Foundation 框架,您的应用程序可能会或可能不会使用它;另一方面,实际的桥接机制需要在编译器中执行,正确地,编译器应该对标准库之上的“高级”库有尽可能少的了解。

因此,编译器对什么知之甚少NSError实际上是,相反,Error暴露三个属性 https://github.com/apple/swift/blob/master/stdlib/public/core/ErrorType.swift#L113它提供了完整的底层表示NSError:

public protocol Error {
  var _domain: String { get }
  var _code: Int { get }

  // Note: _userInfo is always an NSDictionary, but we cannot use that type here
  // because the standard library cannot depend on Foundation. However, the
  // underscore implies that we control all implementations of this requirement.
  var _userInfo: AnyObject? { get }

  // ...
}

NSError,那么,有一个Swift 扩展符合Error并实现这三个属性 https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/NSError.swift#L302:

extension NSError : Error {
  @nonobjc
  public var _domain: String { return domain }

  @nonobjc
  public var _code: Int { return code }

  @nonobjc
  public var _userInfo: AnyObject? { return userInfo as NSDictionary }

  // ...
}

有了这个,当你import Foundation, any Error可以转换为NSError反之亦然,因为两者都暴露_domain, _code, and _userInfo(这是编译器实际用来执行桥接的)。

The CustomNSError协议通过允许您提供一个来发挥作用errorDomain, errorCode, and errorUserInfo,然后通过各种扩展 https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/NSError.swift#L171作为他们的下划线版本:

public extension Error where Self : CustomNSError {
  /// Default implementation for customized NSErrors.
  var _domain: String { return Self.errorDomain }

  /// Default implementation for customized NSErrors.
  var _code: Int { return self.errorCode }

  // ...
}

那么,怎么样EncodingError and DecodingError不同的?好吧,由于它们都是在标准库中定义的(无论您是否使用 Foundation,它都会存在,并且不能依赖于 Foundation),因此它们通过以下方式挂接到系统中:提供实施_domain, _code, and _userInfo直接地 https://github.com/apple/swift/blob/master/stdlib/public/core/Codable.swift.gyb#L1255.

由于这两种类型都提供这些变量的直接下划线版本,因此它们不会调用非下划线版本来获取域、代码和用户信息 - 直接使用值(而不是依赖于var _domain: String { return Self.errorDomain }).

因此,实际上,您无法覆盖该行为,因为EncodingError and DecodingError已经提供此信息。相反,如果您想提供不同的代码/域/用户信息字典,您将需要编写一个函数,该函数需要EncodingError/DecodingError并返回你自己的NSError,或类似的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 DecodingError 中采用 CustomNSError 的相关文章

随机推荐