Comment fournir une description localisée avec un type d'erreur dans Swift?


203

Je définis un type d'erreur personnalisé avec la syntaxe Swift 3 et je veux fournir une description conviviale de l'erreur qui est renvoyée par la localizedDescriptionpropriété de l' Errorobjet. Comment puis-je le faire?

public enum MyError: Error {
  case customError

  var localizedDescription: String {
    switch self {
    case .customError:
      return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
    }
  }
}

let error: Error = MyError.customError
error.localizedDescription
// "The operation couldn’t be completed. (MyError error 0.)"

Existe-t-il un moyen localizedDescriptionde renvoyer ma description d'erreur personnalisée ("Une description conviviale de l'erreur.")? Notez que l'objet d'erreur ici est de type Erroret non MyError. Je peux, bien sûr, lancer l'objet sur MyError

(error as? MyError)?.localizedDescription

mais existe-t-il un moyen de le faire fonctionner sans transtyper mon type d'erreur?

Réponses:


403

Comme décrit dans les notes de version de Xcode 8 beta 6,

Les types d'erreur définis par Swift peuvent fournir des descriptions d'erreurs localisées en adoptant le nouveau protocole LocalizedError.

Dans ton cas:

public enum MyError: Error {
    case customError
}

extension MyError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .customError:
            return NSLocalizedString("A user-friendly description of the error.", comment: "My error")
        }
    }
}

let error: Error = MyError.customError
print(error.localizedDescription) // A user-friendly description of the error.

Vous pouvez fournir encore plus d'informations si l'erreur est convertie en NSError(ce qui est toujours possible):

extension MyError : LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .customError:
            return NSLocalizedString("I failed.", comment: "")
        }
    }
    public var failureReason: String? {
        switch self {
        case .customError:
            return NSLocalizedString("I don't know why.", comment: "")
        }
    }
    public var recoverySuggestion: String? {
        switch self {
        case .customError:
            return NSLocalizedString("Switch it off and on again.", comment: "")
        }
    }
}

let error = MyError.customError as NSError
print(error.localizedDescription)        // I failed.
print(error.localizedFailureReason)      // Optional("I don\'t know why.")
print(error.localizedRecoverySuggestion) // Optional("Switch it off and on again.")

En adoptant le CustomNSErrorprotocole, l'erreur peut fournir un userInfodictionnaire (et aussi un domainet code). Exemple:

extension MyError: CustomNSError {

    public static var errorDomain: String {
        return "myDomain"
    }

    public var errorCode: Int {
        switch self {
        case .customError:
            return 999
        }
    }

    public var errorUserInfo: [String : Any] {
        switch self {
        case .customError:
            return [ "line": 13]
        }
    }
}

let error = MyError.customError as NSError

if let line = error.userInfo["line"] as? Int {
    print("Error in line", line) // Error in line 13
}

print(error.code) // 999
print(error.domain) // myDomain

7
Y a-t-il une raison pour laquelle vous faites MyErrorune Errorpremière et la prolongez avec LocalizedErrorplus tard? Y a-t-il une différence si vous en avez fait un LocalizedErroren premier lieu?
Gee.E

9
@ Gee.E: Cela ne fait aucune différence. C'est juste un moyen d'organiser le code (une extension pour chaque protocole). Comparez stackoverflow.com/questions/36263892/… , stackoverflow.com/questions/40502086/… ou natashatherobot.com/using-swift-extensions .
Martin R

4
Ah, vérifiez. Je comprends ce que vous dites maintenant. La section "Protocol Conformance" sur natashatherobot.com/using-swift-extensions est en effet un bon exemple de ce que vous voulez dire. Merci!
Gee.E

1
@MartinR Si mon erreur devait être convertie en NSError, comment puis-je transmettre un dictionnaire d'erreur accessible en tant qu'utilisateurInfo de NSError?
BangOperator

18
Attention à taper var errorDescription: String?au lieu de String. Il y a un bogue dans l'implémentation de LocalizedError. Voir SR-5858 .
ethanhuang13

35

J'ajouterais également, si votre erreur a des paramètres comme celui-ci

enum NetworkError: LocalizedError {
  case responseStatusError(status: Int, message: String)
}

vous pouvez appeler ces paramètres dans votre description localisée comme ceci:

extension NetworkError {
  public var errorDescription: String? {
    switch self {
    case .responseStatusError(status: let status, message: let message):
      return "Error with status \(status) and message \(message) was thrown"
  }
}

Vous pouvez même raccourcir ceci comme ceci:

extension NetworkError {
  public var errorDescription: String? {
    switch self {
    case let .responseStatusError(status, message):
      return "Error with status \(status) and message \(message) was thrown"
  }
}

4

Il existe maintenant deux protocoles adoptant les erreurs que votre type d'erreur peut adopter afin de fournir des informations supplémentaires à Objective-C - LocalizedError et CustomNSError. Voici un exemple d'erreur qui les adopte tous les deux:

enum MyBetterError : CustomNSError, LocalizedError {
    case oops

    // domain
    static var errorDomain : String { return "MyDomain" }
    // code
    var errorCode : Int { return -666 }
    // userInfo
    var errorUserInfo: [String : Any] { return ["Hey":"Ho"] };

    // localizedDescription
    var errorDescription: String? { return "This sucks" }
    // localizedFailureReason
    var failureReason: String? { return "Because it sucks" }
    // localizedRecoverySuggestion
    var recoverySuggestion: String? { return "Give up" }

}

2
Pouvez-vous faire un montage? Vos exemples n'aident pas beaucoup à comprendre la valeur de chacun. Ou supprimez-le simplement parce que la réponse de MartinR offre exactement cela ...
Honey

3

L'utilisation d'une structure peut être une alternative. Un peu d'élégance avec une localisation statique:

import Foundation

struct MyError: LocalizedError, Equatable {

   private var description: String!

   init(description: String) {
       self.description = description
   }

   var errorDescription: String? {
       return description
   }

   public static func ==(lhs: MyError, rhs: MyError) -> Bool {
       return lhs.description == rhs.description
   }
}

extension MyError {

   static let noConnection = MyError(description: NSLocalizedString("No internet connection",comment: ""))
   static let requestFailed = MyError(description: NSLocalizedString("Request failed",comment: ""))
}

func throwNoConnectionError() throws {
   throw MyError.noConnection
}

do {
   try throwNoConnectionError()
}
catch let myError as MyError {
   switch myError {
   case .noConnection:
       print("noConnection: \(myError.localizedDescription)")
   case .requestFailed:
       print("requestFailed: \(myError.localizedDescription)")
   default:
      print("default: \(myError.localizedDescription)")
   }
}

0

Voici une solution plus élégante:

  enum ApiError: String, LocalizedError {

    case invalidCredentials = "Invalid credentials"
    case noConnection = "No connection"

    var localizedDescription: String { return NSLocalizedString(self.rawValue, comment: "") }

  }

4
Cela peut être plus élégant au moment de l'exécution, mais l'étape de localisation statique ne parviendra pas à extraire ces chaînes pour les traducteurs; vous verrez une "Bad entry in file – Argument is not a literal string"erreur lorsque vous exécutez exportLocalizationsou genstringspour créer votre liste de texte traduisible.
savinola

@savinola est d'accord, la localisation statique ne fonctionnera pas dans ce cas. Peut-être que l'utilisation switch + casen'est qu'une option ...
Vitaliy Gozhenko

L'utilisation de valeurs brutes empêchera également l'utilisation de valeurs associées pour l'une de vos erreurs
Brody Robertson
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.