将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)
它的userInfo
是nil
。
我的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)") }
编辑:其实你可以从ErrorType
到NSError
,但是你从默认实现中获得的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>?
从LifeError
到extension 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包装器,用于将ErrorType
为NSError
(通过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我与世界分享。