我正在使用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时单独发送其他用户信息,但我非常想知道我对这种情况的理解在哪里出错。
答案 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
协议允许您提供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挂钩到系统中。
由于两种类型都提供这些变量的直接下划线版本,因此它们不会调用非下划线版本来获取域,代码和用户信息 - 这些值是直接使用的(而不是依赖于{{ 1}})。
因此,实际上,您无法覆盖该行为,因为var _domain: String { return Self.errorDomain }
和EncodingError
已经提供此信息。相反,如果您想提供不同的代码/域/用户信息词典,您将需要编写一个函数,该函数需要DecodingError
/ EncodingError
并返回您自己的DecodingError
,或类似的。