In Swift 2.0 NSError
conforms to the ErrorType
protocol.
For a customly defined error, we can specify the associating object(s) for some cases, like below.
enum LifeError: ErrorType {
case BeBorn
case LostJob(job: String)
case GetCaughtByWife(wife: String)
...
}
We can comfortably do the following:
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
...
}
However if we want it to pass into other places as an NSError
, it loses its associating object information.
println("\(LifeError.GetCaughtByWife("Name") as NSError)")
prints:
Error Domain=... Code=1 "The operation couldn't be completed". (... error 1)
and its userInfo
is nil
.
Where is my wife
associated with the ErrorType
?
New in Xcode 8: 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]
}
}
}
An ErrorType
can't really be casted to an NSError
, you have to take the associated data and package it into an NSError
yourself.
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
throw NSError(domain:LifeErrorDomain code:-1 userInfo:
[NSLocalizedDescriptionKey:"You cheated on \(wife)")
}
EDIT: Actually you can do the cast from ErrorType
to NSError
, but the NSError
you get from the default implementation is quite primitive. What I'm doing in my app is hooking application:willPresentError: in my app delegate and using a custom class to read the my app's ErrorType
's and decorate NSErrors to return.
Creating an NSError
in every catch block can lead to a lot of copy and paste to convert your custom ErrorType
to NSError
. I abstracted it away similar to @powertoold.
protocol CustomErrorConvertible {
func userInfo() -> Dictionary<String,String>?
func errorDomain() -> String
func errorCode() -> Int
}
This extension can hold code, that's common for the LifeError
we already have and other custom error types we may create.
extension CustomErrorConvertible {
func error() -> NSError {
return NSError(domain: self.errorDomain(), code: self.errorCode(), userInfo: self.userInfo())
}
}
Off to the implementation!
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
}
}
}
And this is how to use it.
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)
}
You could easily move certain functions like func userInfo() -> Dictionary<String,String>?
from LifeError
to extension CustomErrorConvertible
or a different extension.
Instead of hardcoding the error codes like above an enum might be preferable.
enum LifeError:Int {
case Born
case LostJob
}
My solution to this problem was to create an enum that conforms to Int, ErrorType:
enum AppError: Int, ErrorType {
case UserNotLoggedIn
case InternetUnavailable
}
And then extend the enum to conform to CustomStringConvertible and a custom protocol called CustomErrorConvertible:
extension AppError: CustomStringConvertible, CustomErrorConvertible
protocol CustomErrorConvertible {
var error: NSError { get }
}
For the description and error, I switched on the AppError. Example:
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
}
And then I composed my own NSError:
return NSError(domain:NSBundle.mainBundle().bundleIdentifier!, code:errorCode, userInfo:[NSLocalizedDescriptionKey: errorDescription])
I'm having this problem too using PromiseKit and I found a workaround that may be a bit ugly but seems to work.
I paste here my playground so you can see the whole process.
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()
The best solution that I found, is to have an Objective-C wrapper for casting the ErrorType
to NSError
(via NSObject*
parmeter) and extracting the userInfo
. Most likely this would work for other associated objects too.
In my case all other attempts using only Swift resulted in getting a nil
userInfo
.
Here is the Objective-C helper. Place it for example in a MyErrorUtils
class exposed to Swift:
+ (NSDictionary*)getUserInfo:(NSObject *)error {
NSError *nsError = (NSError *)error;
if (nsError != nil) {
return [nsError userInfo];
} else {
return nil;
}
}
Then use the helper in Swift like this:
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 ...
}
}
(I'm currently using XCode 8 and Swift 2.3)
As the accepted answer pointed out, there's now CustomNSError
in Swift 3, however, you don't necessarily need to use it. If you define your error type like this
@objc
enum MyErrorType: Int, Error { ... }
Then this error can directly be casted to NSError
:
let error: MyErrorType = ...
let objcError = error as NSError
I just discovered that today and though I share it with the world.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With