如何正确地将 Swift 错误与参数桥接到 NSError



我已经阅读了Swift Evolution关于改进桥接的帖子和其他一些在线资源,但仍然缺少一些东西。

给定此自定义Error枚举:

public enum MyNetworkError: Error {
case networkOffline
case httpError(status: Int)
case unknown
case systemError(errno: Int)
}

一个Objective-C应用程序应该能够读取错误对象并以某种方式提取错误名称(networkOfflinehttpErrorunknownsystemError)和案例参数(httpError.statussystemError.errno)。

将上述内容转换为NSError的结果令人惊讶,我正在尝试了解如何改进:

let nse_A = MyNetworkError.networkOffline as NSError
let nse_B = MyNetworkError.httpError(status: 503) as NSError
let nse_C = MyNetworkError.unknown as NSError
let nse_D = MyNetworkError.systemError(42) as NSError

首先,生成的错误代码。似乎有参数的情况,无论其顺序如何,都从零开始code

print(nse_A.code)  // 2 (expected: 0)
print(nse_B.code)  // 0 (expected: 1)
print(nse_C.code)  // 3 (expected: 2)
print(nse_D.code)  // 1 (expected: 3)

给定应用程序报告的错误代码,现在很难分辨实际的错误情况。

其次,我希望这样的智能机制(特别是因为它是编译器生成的)也将 case 参数复制到userInfo字典中 - 但它没有。

是我做错了,还是必须完全实现CustomNSError协议才能获得有意义且一致的NSError对象?当然,这是一个选项,但我希望它会自动完成(有点像Codable的魔力)。

此外,应用程序是否可以获取错误案例名称作为String

作为参考,上面的片段是在 Xcode 10.3 Playground 中执行的。

简短回答:对于基于整数的枚举错误类型,错误值将按预期映射到NSError代码。对于所有其他错误类型(如具有关联值的枚举),您必须实现CustomNSError协议才能控制NSError代码和用户信息。

一些细节:只有对于基于整数的错误类型,代码才会从 Swift 桥接到NSError,例如:

public enum IntNetworkError: Int, Error {
case networkOffline = 13
case httpError
case unknown
case systemError
}
let err = IntNetworkError.httpError as NSError
print(err.code) // 14

这是在 ErrorType.swift 中实现的特殊情况。对于所有其他错误类型,默认实现在 ErrorDefaultImpls.cpp 中,对于枚举类型返回"标记",对于所有其他类型返回1。例:

struct StringError: Error {}
let serr = StringError() as  NSError
print(serr.code) // 1

枚举"标记"在"类型布局"文档中进行了描述。对于具有关联值的枚举,这不一定遵循声明案例的顺序。这就是为什么您观察到"意外"NSError代码的原因。

因此,正确的方法是实现CustomNSError协议,编译器不会为您合成该协议。

extension MyNetworkError: CustomNSError {
public static var errorDomain: String {
return "MyNetworkError"
}
public var errorCode: Int {
switch self {
case .networkOffline: return 1
case .httpError: return 2
case .unknown: return 3
case .systemError: return 4
}
}
public var errorUserInfo: [String : Any] {
switch self {
case .httpError(let status):
return [ "status": status]
case .systemError(let errno):
return [ "errno": errno]
default:
return [:]
}
}
}
let nse_B = MyNetworkError.httpError(status: 503) as NSError
print(nse_B.code) // 2
print(nse_B.userInfo) // ["status": 503]

最新更新