将 ErrorType 转换为 NSError 会丢失关联对象
Converting ErrorType to NSError loses associated objects
在Swift 2.0中NSError
符合ErrorType
协议。
对于自定义错误,我们可以为某些情况指定关联对象,如下所示。
enum LifeError: ErrorType {
case BeBorn
case LostJob(job: String)
case GetCaughtByWife(wife: String)
...
}
我们可以轻松地做到以下几点:
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
...
}
但是,如果我们希望它作为 NSError
传递到其他地方,它会丢失其关联对象信息。
println("\(LifeError.GetCaughtByWife("Name") as NSError)")
打印:
Error Domain=... Code=1 "The operation couldn't be completed". (... error 1)
它的userInfo
是nil
.
我的 wife
与 ErrorType
关联在哪里?
一个ErrorType
并不能真正转换成一个NSError
,你得自己把关联的数据打包成一个NSError
。
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
throw NSError(domain:LifeErrorDomain code:-1 userInfo:
[NSLocalizedDescriptionKey:"You cheated on \(wife)")
}
编辑:实际上您可以将 ErrorType
转换为 NSError
,但是您从默认实现中获得的 NSError
非常原始。我在我的应用程序中所做的是挂钩 application:willPresentError:在我的应用程序委托中并使用自定义 class 读取我的应用程序的 ErrorType
并将 NSErrors 装饰为 return .
我对这个问题的解决方案是创建一个符合 Int、ErrorType 的枚举:
enum AppError: Int, ErrorType {
case UserNotLoggedIn
case InternetUnavailable
}
然后扩展枚举以符合 CustomStringConvertible 和名为 CustomErrorConvertible 的自定义协议:
extension AppError: CustomStringConvertible, CustomErrorConvertible
protocol CustomErrorConvertible {
var error: NSError { get }
}
对于描述和错误,我打开了AppError。示例:
Description: switch self {
case .UserNotLoggedIn: return NSLocalizedString("ErrorUserNotLoggedIn", comment: "User not logged into cloud account.")
case .InternetUnavailable: return NSLocalizedString("ErrorInternetUnavailable", comment: "Internet connection not available.")
}
Error: switch self {
case .UserNotLoggedIn: errorCode = UserNotLoggedIn.rawValue; errorDescription = UserNotLoggedIn.description
case .InternetUnavailable: errorCode = InternetUnavailable.rawValue; errorDescription = InternetUnavailable.description
}
然后我编写了自己的 NSError:
return NSError(domain:NSBundle.mainBundle().bundleIdentifier!, code:errorCode, userInfo:[NSLocalizedDescriptionKey: errorDescription])
我在使用 PromiseKit 时也遇到了这个问题,我找到了一个可能有点难看但似乎有效的解决方法。
我把我的游乐场贴在这里,这样你就可以看到整个过程。
import Foundation
import PromiseKit
import XCPlayground
let error = NSError(domain: "a", code: 1, userInfo: ["hello":"hello"])
// Only casting won't lose the user info
let castedError = error as ErrorType
let stillHaveUserInfo = castedError as NSError
// when using promises
func convert(error: ErrorType) -> Promise<Int> {
return Promise<Int> {
(fulfill, reject) in
reject(error)
}
}
let promiseA = convert(error)
// Seems to lose the user info once we cast back to NSError
promiseA.report { (promiseError) -> Void in
let lostUserInfo = promiseError as NSError
}
// Workaround
protocol CastingNSErrorHelper {
var userInfo: [NSObject : AnyObject] { get }
}
extension NSError : CastingNSErrorHelper {}
promiseA.report { (promiseError) -> Void in
let castingNSErrorHelper = promiseError as! CastingNSErrorHelper
let recoveredErrorWithUserInfo = castingNSErrorHelper as! NSError
}
XCPSetExecutionShouldContinueIndefinitely()
在每个 catch 块中创建一个 NSError
会导致进行大量复制和粘贴操作以将您的自定义 ErrorType
转换为 NSError
。我把它抽象出来类似于 .
protocol CustomErrorConvertible {
func userInfo() -> Dictionary<String,String>?
func errorDomain() -> String
func errorCode() -> Int
}
此扩展程序可以保存代码,这对于我们已有的 LifeError
和我们可能创建的其他自定义错误类型很常见。
extension CustomErrorConvertible {
func error() -> NSError {
return NSError(domain: self.errorDomain(), code: self.errorCode(), userInfo: self.userInfo())
}
}
开始实施!
enum LifeError: ErrorType, CustomErrorConvertible {
case BeBorn
case LostJob(job: String)
case GetCaughtByPolice(police: String)
func errorDomain() -> String {
return "LifeErrorDomain"
}
func userInfo() -> Dictionary<String,String>? {
var userInfo:Dictionary<String,String>?
if let errorString = errorDescription() {
userInfo = [NSLocalizedDescriptionKey: errorString]
}
return userInfo
}
func errorDescription() -> String? {
var errorString:String?
switch self {
case .LostJob(let job):
errorString = "fired as " + job
case .GetCaughtByPolice(let cops):
errorString = "arrested by " + cops
default:
break;
}
return errorString
}
func errorCode() -> Int {
switch self {
case .BeBorn:
return 1
case .LostJob(_):
return -9000
case .GetCaughtByPolice(_):
return 50
}
}
}
这就是使用方法。
func lifeErrorThrow() throws {
throw LifeError.LostJob(job: "L33tHax0r")
}
do {
try lifeErrorThrow()
}
catch LifeError.BeBorn {
print("vala morgulis")
}
catch let myerr as LifeError {
let error = myerr.error()
print(error)
}
您可以轻松地将某些功能(例如 func userInfo() -> Dictionary<String,String>?
从 LifeError
移动到 extension CustomErrorConvertible
或其他扩展名。
与其像上面那样对错误代码进行硬编码,不如使用枚举。
enum LifeError:Int {
case Born
case LostJob
}
Xcode8 中的新内容:CustomNSError
protocol.
enum LifeError: CustomNSError {
case beBorn
case lostJob(job: String)
case getCaughtByWife(wife: String)
static var errorDomain: String {
return "LifeError"
}
var errorCode: Int {
switch self {
case .beBorn:
return 0
case .lostJob(_):
return 1
case .getCaughtByWife(_):
return 2
}
}
var errorUserInfo: [String : AnyObject] {
switch self {
case .beBorn:
return [:]
case .lostJob(let job):
return ["Job": job]
case .getCaughtByWife(let wife):
return ["Wife": wife]
}
}
}
我找到的最佳解决方案是使用 Objective-C 包装器将 ErrorType
转换为 NSError
(通过 NSObject*
参数)并提取 userInfo
。这很可能也适用于其他关联对象。
在我的例子中,所有其他仅使用 Swift 的尝试导致了 nil
userInfo
.
这是 Objective-C 助手。例如,将它放在暴露于 Swift:
的 MyErrorUtils
class 中
+ (NSDictionary*)getUserInfo:(NSObject *)error {
NSError *nsError = (NSError *)error;
if (nsError != nil) {
return [nsError userInfo];
} else {
return nil;
}
}
然后像这样使用 Swift 中的助手:
static func myErrorHandler(error: ErrorType) {
// Note the as? cast to NSObject
if let userInfo: [NSObject: AnyObject]? =
MyErrorUtils.getUserInfo(error as? NSObject) {
let myUserInfo = userInfo["myCustomUserInfo"]
// ... Error processing based on userInfo ...
}
}
(我目前使用的是 XCode 8 和 Swift 2.3)
正如已接受的答案所指出的,Swift 3 中现在有 CustomNSError
,但是,您不一定需要使用它。如果您这样定义错误类型
@objc
enum MyErrorType: Int, Error { ... }
那么这个错误可以直接转换为NSError
:
let error: MyErrorType = ...
let objcError = error as NSError
我今天才发现这一点,并与全世界分享。
在Swift 2.0中NSError
符合ErrorType
协议。
对于自定义错误,我们可以为某些情况指定关联对象,如下所示。
enum LifeError: ErrorType {
case BeBorn
case LostJob(job: String)
case GetCaughtByWife(wife: String)
...
}
我们可以轻松地做到以下几点:
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
...
}
但是,如果我们希望它作为 NSError
传递到其他地方,它会丢失其关联对象信息。
println("\(LifeError.GetCaughtByWife("Name") as NSError)")
打印:
Error Domain=... Code=1 "The operation couldn't be completed". (... error 1)
它的userInfo
是nil
.
我的 wife
与 ErrorType
关联在哪里?
一个ErrorType
并不能真正转换成一个NSError
,你得自己把关联的数据打包成一个NSError
。
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
throw NSError(domain:LifeErrorDomain code:-1 userInfo:
[NSLocalizedDescriptionKey:"You cheated on \(wife)")
}
编辑:实际上您可以将 ErrorType
转换为 NSError
,但是您从默认实现中获得的 NSError
非常原始。我在我的应用程序中所做的是挂钩 application:willPresentError:在我的应用程序委托中并使用自定义 class 读取我的应用程序的 ErrorType
并将 NSErrors 装饰为 return .
我对这个问题的解决方案是创建一个符合 Int、ErrorType 的枚举:
enum AppError: Int, ErrorType {
case UserNotLoggedIn
case InternetUnavailable
}
然后扩展枚举以符合 CustomStringConvertible 和名为 CustomErrorConvertible 的自定义协议:
extension AppError: CustomStringConvertible, CustomErrorConvertible
protocol CustomErrorConvertible {
var error: NSError { get }
}
对于描述和错误,我打开了AppError。示例:
Description: switch self {
case .UserNotLoggedIn: return NSLocalizedString("ErrorUserNotLoggedIn", comment: "User not logged into cloud account.")
case .InternetUnavailable: return NSLocalizedString("ErrorInternetUnavailable", comment: "Internet connection not available.")
}
Error: switch self {
case .UserNotLoggedIn: errorCode = UserNotLoggedIn.rawValue; errorDescription = UserNotLoggedIn.description
case .InternetUnavailable: errorCode = InternetUnavailable.rawValue; errorDescription = InternetUnavailable.description
}
然后我编写了自己的 NSError:
return NSError(domain:NSBundle.mainBundle().bundleIdentifier!, code:errorCode, userInfo:[NSLocalizedDescriptionKey: errorDescription])
我在使用 PromiseKit 时也遇到了这个问题,我找到了一个可能有点难看但似乎有效的解决方法。
我把我的游乐场贴在这里,这样你就可以看到整个过程。
import Foundation
import PromiseKit
import XCPlayground
let error = NSError(domain: "a", code: 1, userInfo: ["hello":"hello"])
// Only casting won't lose the user info
let castedError = error as ErrorType
let stillHaveUserInfo = castedError as NSError
// when using promises
func convert(error: ErrorType) -> Promise<Int> {
return Promise<Int> {
(fulfill, reject) in
reject(error)
}
}
let promiseA = convert(error)
// Seems to lose the user info once we cast back to NSError
promiseA.report { (promiseError) -> Void in
let lostUserInfo = promiseError as NSError
}
// Workaround
protocol CastingNSErrorHelper {
var userInfo: [NSObject : AnyObject] { get }
}
extension NSError : CastingNSErrorHelper {}
promiseA.report { (promiseError) -> Void in
let castingNSErrorHelper = promiseError as! CastingNSErrorHelper
let recoveredErrorWithUserInfo = castingNSErrorHelper as! NSError
}
XCPSetExecutionShouldContinueIndefinitely()
在每个 catch 块中创建一个 NSError
会导致进行大量复制和粘贴操作以将您的自定义 ErrorType
转换为 NSError
。我把它抽象出来类似于
protocol CustomErrorConvertible {
func userInfo() -> Dictionary<String,String>?
func errorDomain() -> String
func errorCode() -> Int
}
此扩展程序可以保存代码,这对于我们已有的 LifeError
和我们可能创建的其他自定义错误类型很常见。
extension CustomErrorConvertible {
func error() -> NSError {
return NSError(domain: self.errorDomain(), code: self.errorCode(), userInfo: self.userInfo())
}
}
开始实施!
enum LifeError: ErrorType, CustomErrorConvertible {
case BeBorn
case LostJob(job: String)
case GetCaughtByPolice(police: String)
func errorDomain() -> String {
return "LifeErrorDomain"
}
func userInfo() -> Dictionary<String,String>? {
var userInfo:Dictionary<String,String>?
if let errorString = errorDescription() {
userInfo = [NSLocalizedDescriptionKey: errorString]
}
return userInfo
}
func errorDescription() -> String? {
var errorString:String?
switch self {
case .LostJob(let job):
errorString = "fired as " + job
case .GetCaughtByPolice(let cops):
errorString = "arrested by " + cops
default:
break;
}
return errorString
}
func errorCode() -> Int {
switch self {
case .BeBorn:
return 1
case .LostJob(_):
return -9000
case .GetCaughtByPolice(_):
return 50
}
}
}
这就是使用方法。
func lifeErrorThrow() throws {
throw LifeError.LostJob(job: "L33tHax0r")
}
do {
try lifeErrorThrow()
}
catch LifeError.BeBorn {
print("vala morgulis")
}
catch let myerr as LifeError {
let error = myerr.error()
print(error)
}
您可以轻松地将某些功能(例如 func userInfo() -> Dictionary<String,String>?
从 LifeError
移动到 extension CustomErrorConvertible
或其他扩展名。
与其像上面那样对错误代码进行硬编码,不如使用枚举。
enum LifeError:Int {
case Born
case LostJob
}
Xcode8 中的新内容:CustomNSError
protocol.
enum LifeError: CustomNSError {
case beBorn
case lostJob(job: String)
case getCaughtByWife(wife: String)
static var errorDomain: String {
return "LifeError"
}
var errorCode: Int {
switch self {
case .beBorn:
return 0
case .lostJob(_):
return 1
case .getCaughtByWife(_):
return 2
}
}
var errorUserInfo: [String : AnyObject] {
switch self {
case .beBorn:
return [:]
case .lostJob(let job):
return ["Job": job]
case .getCaughtByWife(let wife):
return ["Wife": wife]
}
}
}
我找到的最佳解决方案是使用 Objective-C 包装器将 ErrorType
转换为 NSError
(通过 NSObject*
参数)并提取 userInfo
。这很可能也适用于其他关联对象。
在我的例子中,所有其他仅使用 Swift 的尝试导致了 nil
userInfo
.
这是 Objective-C 助手。例如,将它放在暴露于 Swift:
的MyErrorUtils
class 中
+ (NSDictionary*)getUserInfo:(NSObject *)error {
NSError *nsError = (NSError *)error;
if (nsError != nil) {
return [nsError userInfo];
} else {
return nil;
}
}
然后像这样使用 Swift 中的助手:
static func myErrorHandler(error: ErrorType) {
// Note the as? cast to NSObject
if let userInfo: [NSObject: AnyObject]? =
MyErrorUtils.getUserInfo(error as? NSObject) {
let myUserInfo = userInfo["myCustomUserInfo"]
// ... Error processing based on userInfo ...
}
}
(我目前使用的是 XCode 8 和 Swift 2.3)
正如已接受的答案所指出的,Swift 3 中现在有 CustomNSError
,但是,您不一定需要使用它。如果您这样定义错误类型
@objc
enum MyErrorType: Int, Error { ... }
那么这个错误可以直接转换为NSError
:
let error: MyErrorType = ...
let objcError = error as NSError
我今天才发现这一点,并与全世界分享。