在 DecodingError 中采用 CustomNSError
Adopting CustomNSError in DecodingError
我正在使用 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 { [=10=].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 "\([=10=].key): \(String(describing: [=10=].value))"
}.joined(separator: ", ")
return userInfo
case .keyNotFound(let codingKey, let context):
return [
"debugDescription": context.debugDescription,
"codingPath": context.codingPath.map { [=10=].stringValue }.joined(separator: "."),
"codingKey": codingKey.stringValue
]
case .typeMismatch(_, let context), .valueNotFound(_, let context):
return [
"debugDescription": context.debugDescription,
"codingPath": context.codingPath.map { [=10=].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 框架,您的应用程序可能会使用也可能不会;另一方面,实际的桥接机制需要在编译器中执行,并且正确地,编译器应该尽可能少地了解标准库之上的 "high-level" 库。
因此,编译器对 NSError
实际是什么知之甚少,相反,Error
exposes three properties 提供了 NSError
:[=45 的全部底层表示=]
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.
连接到系统中
由于这两种类型都提供了这些变量的直接下划线版本,它们不会调用非下划线版本来获取域、代码和用户信息——这些值是直接使用的(而不是依赖于var _domain: String { return Self.errorDomain }
).
因此,实际上,您无法覆盖该行为,因为 EncodingError
和 DecodingError
已经提供了此信息。相反,如果你想提供不同的 codes/domains/user 信息字典,你将需要编写一个函数,它接受一个 EncodingError
/DecodingError
和 returns 你自己的 NSError
,或类似的。
我正在使用 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 { [=10=].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 "\([=10=].key): \(String(describing: [=10=].value))"
}.joined(separator: ", ")
return userInfo
case .keyNotFound(let codingKey, let context):
return [
"debugDescription": context.debugDescription,
"codingPath": context.codingPath.map { [=10=].stringValue }.joined(separator: "."),
"codingKey": codingKey.stringValue
]
case .typeMismatch(_, let context), .valueNotFound(_, let context):
return [
"debugDescription": context.debugDescription,
"codingPath": context.codingPath.map { [=10=].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 框架,您的应用程序可能会使用也可能不会;另一方面,实际的桥接机制需要在编译器中执行,并且正确地,编译器应该尽可能少地了解标准库之上的 "high-level" 库。
因此,编译器对 NSError
实际是什么知之甚少,相反,Error
exposes three properties 提供了 NSError
:[=45 的全部底层表示=]
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.
由于这两种类型都提供了这些变量的直接下划线版本,它们不会调用非下划线版本来获取域、代码和用户信息——这些值是直接使用的(而不是依赖于var _domain: String { return Self.errorDomain }
).
因此,实际上,您无法覆盖该行为,因为 EncodingError
和 DecodingError
已经提供了此信息。相反,如果你想提供不同的 codes/domains/user 信息字典,你将需要编写一个函数,它接受一个 EncodingError
/DecodingError
和 returns 你自己的 NSError
,或类似的。