将ErrorType转换为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) 

它的userInfonil

我的wife在哪里与ErrorType关联?

Xcode 8中的新增内容CustomNSError 协议 。

 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] } } } 

一个ErrorType不能真正被转换成一个NSError ,你必须把相关的数据自己封装到一个NSError

 do { try haveAffairWith(otherPerson) } catch LifeError.GetCaughtByWife(let wife) { throw NSError(domain:LifeErrorDomain code:-1 userInfo: [NSLocalizedDescriptionKey:"You cheated on \(wife)") } 

编辑:其实你可以从ErrorTypeNSError ,但是你从默认实现中获得的NSError是相当原始的。 我在我的应用程序中做的是钩应用程序:willPresentError:在我的应用程序委托,并使用自定义类来读取我的应用程序的ErrorType的和装饰NSErrors返回。

在每个catch块中创build一个NSError可能会导致大量的复制和粘贴操作,将您自定义的ErrorType转换为NSError 。 我把它抽象出来,就像@powertoold 。

 protocol CustomErrorConvertible { func userInfo() -> Dictionary<String,String>? func errorDomain() -> String func errorCode() -> Int } 

这个扩展可以保存代码,这对于我们已经拥有的LifeError和我们可能创build的其他自定义错误types是很常见的。

 extension CustomErrorConvertible { func error() -> NSError { return NSError(domain: self.errorDomain(), code: self.errorCode(), userInfo: self.userInfo()) } } 

closures执行!

 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) } 

你可以很容易地移动某些function,如func userInfo() -> Dictionary<String,String>?LifeErrorextension CustomErrorConvertible或不同的扩展。

上面的错误代码,而不是硬编码可能是更好的枚举。

 enum LifeError:Int { case Born case LostJob } 

我解决这个问题的方法是创build一个符合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() 

我发现最好的解决scheme是有一个Objective-C包装器,用于将ErrorTypeNSError (通过NSObject* ErrorType )并提取userInfo 。 这很可能也适用于其他关联的对象。

在我的情况下,所有其他尝试只使用Swift导致得到一个nil userInfo

这是Objective-C的帮手。 将其放置在暴露给Swift的MyErrorUtils类中:

 + (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 ,但是,你不一定需要使用它。 如果你这样定义你的错误types

 @objc enum MyErrorType: Int, Error { ... } 

那么这个错误可以直接传给NSError

 let error: MyErrorType = ... let objcError = error as NSError 

我今天才发现,尽pipe我与世界分享。