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

noa*_*mtm 3 cocoa nserror ios swift

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

鉴于此自定义Error枚举:

    public enum MyNetworkError: Error {
        case networkOffline
        case httpError(status: Int)
        case unknown
        case systemError(errno: Int)
    }
Run Code Online (Sandbox Code Playgroud)

Objective-C 应用程序应该能够读取错误对象并以某种方式提取错误名称 ( networkOffline, httpError, unknown, systemError) 和 case 参数 ( httpError.statusand systemError.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
Run Code Online (Sandbox Code Playgroud)

首先是生成的错误代码。似乎有参数的案例,无论顺序如何,都是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)
Run Code Online (Sandbox Code Playgroud)

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

其次,我期望这种智能机制(尤其是因为它是编译器生成的)也将 case 参数复制到userInfo字典中 - 但事实并非如此。

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

此外,应用程序能否将错误案例名称作为String?

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

Mar*_*n R 5

简短回答: 对于基于整数的枚举错误类型,错误值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
Run Code Online (Sandbox Code Playgroud)

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

struct StringError: Error {}

let serr = StringError() as  NSError
print(serr.code) // 1
Run Code Online (Sandbox Code Playgroud)

枚举“标签”在类型布局文档中进行了描述。对于具有关联值的枚举,这不一定遵循声明案例的顺序。这就是您观察到“意外”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]
Run Code Online (Sandbox Code Playgroud)