代码之家  ›  专栏  ›  技术社区  ›  mike

在DecodingError中采用CustomNSError

  •  4
  • mike  · 技术社区  · 7 年前

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

    使用Crashlytics记录非致命错误时,API需要一个符合错误的对象和可选的用户信息字典。目前我正在查看JSON解码错误,我对Crashlytics仪表板中看到的内容不太满意,当时我刚刚在recordError中发送了DecodingError。因此,我的解决方案是为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
        }
    }
    

    但是传递到日志的对象(\uerror:)不是我的CustomNSError实现,看起来像是NSCocoaErrorDomain的标准NSError。

    我希望这足够详细,可以解释我的意思,不知道为什么传递给log的对象没有我在DecodingError扩展中设置的值。我知道我可以很容易地在与Crashlytics的通话中单独发送额外的用户信息,但我很想知道我对这个场景的理解有何错误。

    1 回复  |  直到 7 年前
        1
  •  3
  •   Itai Ferber    7 年前

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

    因此,编译器对 N错误 实际上是,相反, Error exposes three properties 它提供了 N错误 :

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

    N错误 ,则具有 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 任何 错误 可以转换为 N错误 反之亦然,因为两者都暴露 _domain , _code _userInfo (这是编译器实际用于执行桥接的内容)。

    这个 CustomNSError 协议通过允许您提供 errorDomain , errorCode errorUserInfo ,然后通过 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 }
    
      // ...
    }
    

    那么,你怎么样 EncodingError DecodingError 不同的好吧,因为它们都是在标准库中定义的(无论您是否使用Foundation,标准库都存在,并且不能依赖于Foundation),所以它们通过 providing implementations of _domain , _code , and _userInfo directly .

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

    因此,实际上,您不能重写该行为,因为 编码错误 解码错误 已提供此信息。相反,如果您想提供不同的代码/域/用户信息字典,则需要编写一个函数,该函数采用 编码错误 / 解码错误 并返回您自己的 N错误 ,或类似。