Принятие CustomNSError в DecodingError
Я пишу журнал ошибок, используя Crashlytics, и я столкнулся с проблемой, которая заставляет меня сомневаться в моем понимании протоколов и динамической диспетчеризации.
При записи неустранимых ошибок с помощью Crashlytics API ожидает объект, соответствующий ошибке, и дополнительный словарь пользовательских данных. Я смотрю на ошибки декодирования JSON в данный момент, и я не был слишком доволен тем, что я видел в приборной панели Crashlytics, когда я только что отправил DecodingError в recordError. Так мое решение состояло в том, чтобы написать расширение для 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, выглядит как стандартный NSError с NSCocoaErrorDomain.
Я надеюсь, что это достаточно подробно, чтобы объясните, что я имею в виду, не уверен, почему объект, передаваемый в log, не имеет значений, которые я установил в расширении для DecodingError. Я знаю, что легко могу просто отправить дополнительную информацию о пользователе отдельно в своем вызове Crashlytics, но мне очень хотелось бы знать, где я ошибаюсь в своем понимании этого сценария.
1 ответ:
NSError
bridging-интересный зверь в компиляторе Swift. С одной стороны,NSError
происходит из базового фреймворка, который ваше приложение может использовать, а может и не использовать; с другой стороны, фактическая механика наведения мостов должна выполняться в компиляторе, и по праву компилятор должен иметь как можно меньше знаний о "высокоуровневых" библиотеках выше стандартной библиотеки.Как таковой компилятор имеет очень мало знаний о том, что такое
NSError
На самом деле, и вместо этогоError
раскрывает три свойства , которые обеспечивают полноту базового представления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
, Затем, имеет быстрое расширение, которое соответствуетError
и реализует эти три свойства :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
, которые затем выставляются различными расширениями как их версии подчеркивания:Так чем же отличаются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), они подключаются к системе путем предоставления реализаций_domain
,_code
, и_userInfo
непосредственно .Поскольку оба типа предоставьте прямые версии подчеркивания этих переменных, они не вызывают версии без подчеркивания, чтобы получить домен, код и информацию о пользователе-значения используются напрямую (а не полагаются на
Таким образом, фактически вы не можете переопределить поведение, потому чтоvar _domain: String { return Self.errorDomain }
).EncodingError
иDecodingError
уже предоставляют эту информацию. Вместо этого, если вы хотите предоставить различные коды/Домены / словари информации о пользователе, вам нужно написать функцию, которая принимаетEncodingError
/DecodingError
и возвращает свой собственныйNSError
или что-то подобное.