In Swift 2.0 NSError
est conforme au protocole ErrorType
.
Pour une erreur définie de manière personnalisée, nous pouvons spécifier le ou les objets associés dans certains cas, comme ci-dessous.
enum LifeError: ErrorType {
case BeBorn
case LostJob(job: String)
case GetCaughtByWife(wife: String)
...
}
Nous pouvons confortablement faire ce qui suit:
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
...
}
Cependant, si nous voulons qu'il passe à d'autres endroits en tant que NSError
, il perd ses informations d'objet d'association.
println("\(LifeError.GetCaughtByWife("Name") as NSError)")
impressions:
Error Domain=... Code=1 "The operation couldn't be completed". (... error 1)
et son userInfo
est nil
.
Où mon wife
est-il associé au ErrorType
?
Nouveau dans Xcode 8 : CustomNSError
protocole .
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]
}
}
}
Un ErrorType
ne peut pas vraiment être converti en NSError
, vous devez prendre les données associées et les empaqueter dans un NSError
vous-même.
do {
try haveAffairWith(otherPerson)
} catch LifeError.GetCaughtByWife(let wife) {
throw NSError(domain:LifeErrorDomain code:-1 userInfo:
[NSLocalizedDescriptionKey:"You cheated on \(wife)")
}
EDIT: En fait, vous pouvez faire le cast de ErrorType
à NSError
, mais le NSError
que vous obtenez de l'implémentation par défaut est assez primitif. Ce que je fais dans mon application, c'est accrocher l'application: willPresentError: dans mon délégué d'application et utiliser une classe personnalisée pour lire les ErrorType
de mon application et décorer les NSErrors à retourner.
La création d'un NSError
dans chaque bloc catch peut entraîner de nombreux copier-coller pour convertir votre ErrorType
personnalisé en NSError
. Je l'ai résumé de manière similaire à @ powertoold .
protocol CustomErrorConvertible {
func userInfo() -> Dictionary<String,String>?
func errorDomain() -> String
func errorCode() -> Int
}
Cette extension peut contenir du code, ce qui est courant pour les LifeError
que nous avons déjà et d'autres types d'erreur personnalisés que nous pouvons créer.
extension CustomErrorConvertible {
func error() -> NSError {
return NSError(domain: self.errorDomain(), code: self.errorCode(), userInfo: self.userInfo())
}
}
En route pour l'implémentation!
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
}
}
}
Et voici comment l'utiliser.
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)
}
Vous pouvez facilement déplacer certaines fonctions comme func userInfo() -> Dictionary<String,String>?
de LifeError
vers extension CustomErrorConvertible
Ou une autre extension.
Au lieu de coder en dur les codes d'erreur comme ci-dessus, une énumération peut être préférable.
enum LifeError:Int {
case Born
case LostJob
}
Ma solution à ce problème a été de créer une énumération conforme à Int, ErrorType:
enum AppError: Int, ErrorType {
case UserNotLoggedIn
case InternetUnavailable
}
Et puis étendez l'énumération pour vous conformer à CustomStringConvertible et à un protocole personnalisé appelé CustomErrorConvertible:
extension AppError: CustomStringConvertible, CustomErrorConvertible
protocol CustomErrorConvertible {
var error: NSError { get }
}
Pour la description et l'erreur, j'ai allumé l'AppError. Exemple:
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
}
Et puis j'ai composé ma propre NSError:
return NSError(domain:NSBundle.mainBundle().bundleIdentifier!, code:errorCode, userInfo:[NSLocalizedDescriptionKey: errorDescription])
La meilleure solution que j'ai trouvée est d'avoir un wrapper Objective-C pour convertir le ErrorType
en NSError
(via NSObject*
parmeter) et extraire le userInfo
. Très probablement, cela fonctionnerait également pour d'autres objets associés.
Dans mon cas, toutes les autres tentatives utilisant uniquement Swift ont abouti à l'obtention d'un nil
userInfo
.
Voici l'assistant Objective-C. Placez-le par exemple dans une classe MyErrorUtils
exposée à Swift:
+ (NSDictionary*)getUserInfo:(NSObject *)error {
NSError *nsError = (NSError *)error;
if (nsError != nil) {
return [nsError userInfo];
} else {
return nil;
}
}
Ensuite, utilisez l'aide dans Swift comme ceci:
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 ...
}
}
(J'utilise actuellement XCode 8 et Swift 2.3)
J'ai aussi ce problème en utilisant PromiseKit et j'ai trouvé une solution de contournement qui peut être un peu moche mais qui semble fonctionner.
Je colle ici mon terrain de jeu pour que vous puissiez voir tout le processus.
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()
Comme l'a souligné la réponse acceptée, il y a maintenant CustomNSError
dans Swift 3, cependant, vous n'avez pas nécessairement besoin de l'utiliser. Si vous définissez votre type d'erreur comme ceci
@objc
enum MyErrorType: Int, Error { ... }
Ensuite, cette erreur peut être directement convertie en NSError
:
let error: MyErrorType = ...
let objcError = error as NSError
Je viens de le découvrir aujourd'hui et je le partage avec le monde.