如何正确桥接 Swift 带有参数的错误到 NSError

How to correctly bridge Swift Errors with arguments to NSError

我已经阅读了 Swift Evolution post on the improved bridging 和其他一些在线资源,但仍然缺少一些内容。

鉴于此自定义 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. For all other error types, the default implementation is in ErrorDefaultImpls.cpp 中实现的特殊情况,returns 枚举类型的“标签”,以及所有其他类型的 1。示例:

struct StringError: Error {}

let serr = StringError() as  NSError
print(serr.code) // 1

Type Layout 文档中描述了枚举“标签”。对于具有关联值的枚举,这不一定遵循声明案例的顺序。这就是您观察到“意外”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]