在DecodingError

时间:2018-01-19 15:56:23

标签: swift crashlytics swift-protocols dynamic-dispatch jsondecoder

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

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

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
    }
}

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

我希望这个详细到足以解释我的意思,不知道为什么传递给log的对象没有我在DecodingError扩展中设置的值。我知道我可以轻松地在我打电话给Crashlytics时单独发送其他用户信息,但我非常想知道我对这种情况的理解在哪里出错。

1 个答案:

答案 0 :(得分:2)

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

因此,编译器几乎不了解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协议允许您提供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挂钩到系统中。

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

因此,实际上,您无法覆盖该行为,因为var _domain: String { return Self.errorDomain }EncodingError已经提供此信息。相反,如果您想提供不同的代码/域/用户信息词典,您将需要编写一个函数,该函数需要DecodingError / EncodingError并返回您自己的DecodingError,或类似的。