swift - 在 DecodingError 中采用 CustomNSError

标签 swift crashlytics swift-protocols dynamic-dispatch jsondecoder

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

当使用 Crashlytics 记录非 fatal error 时,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。

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

最佳答案

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

因此,编译器对 NSError 到底是什么知之甚少,相反,Error exposes three properties它提供了 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 extension which conforms to Error and implements those three properties :

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时,任何Error都可以转换为NSError,反之亦然,因为两者都公开了 _domain_code_userInfo(编译器实际使用它来执行桥接)。

CustomNSError 协议(protocol)通过允许您提供 errorDomainerrorCodeerrorUserInfo 发挥作用,然后由 various extensions 公开作为他们的下划线版本:

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 }

  // ...
}

那么,EncodingErrorDecodingError 有何不同?好吧,因为它们都是在标准库中定义的(无论您是否使用 Foundation,它都存在,并且不能依赖于 Foundation),它们通过 providing implementations of _domain, _code, and _userInfo directly 连接到系统中。 .

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

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

关于swift - 在 DecodingError 中采用 CustomNSError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48344928/

相关文章:

firebase - 如何将映射文件上传到 Firebase Crashlytics?

ios - 当您将 self 分配给委托(delegate)时,究竟会发生什么?

swift - 使用从另一个协议(protocol)继承或使用 where Self 声明 swift 协议(protocol)时的区别

swift - 快速解码 base64 编码的应用引擎数据的问题

swift - 使用 Result<T> 调用函数

ios - Fabric/Crashlytics-没有适用于iOS的数据和崩溃报告

属性符合协议(protocol)的 Swift 协议(protocol)扩展

swift - 泛型是在编译期间专门化的,还是它们就像 java 泛型一样仅用于编译时检查?

ios - 想要在后台模式下运行广告外围设备iOS Swift

crashlytics - Fabric 自动上传丢失的 dSYM