Le moyen le plus simple de lancer une erreur / exception avec un message personnalisé dans Swift 2?

136

Je veux faire quelque chose dans Swift 2 que j'ai l'habitude de faire dans plusieurs autres langues: lancer une exception d'exécution avec un message personnalisé. Par exemple (en Java):

throw new RuntimeException("A custom message here")

Je comprends que je peux lancer des types enum conformes au protocole ErrorType, mais je ne veux pas avoir à définir des énumérations pour chaque type d'erreur que je lance. Idéalement, j'aimerais pouvoir imiter au plus près l'exemple ci-dessus. J'ai cherché à créer une classe personnalisée qui implémente le protocole ErrorType, mais je ne peux même pas comprendre ce que ce protocole nécessite (voir la documentation ). Des idées?

markdb314
la source
2
Le lancer / attraper Swift 2 ne fait pas exception.
zaph

Réponses:

194

L'approche la plus simple consiste probablement à définir une personnalisation enumavec une seule casequi a un Stringattaché:

enum MyError: ErrorType {
    case runtimeError(String)
}

Ou, à partir de Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

Un exemple d'utilisation serait quelque chose comme:

func someFunction() throws {
    throw MyError.runtimeError("some message")
}
do {
    try someFunction()
} catch MyError.runtimeError(let errorMessage) {
    print(errorMessage)
}

Si vous souhaitez utiliser des Errortypes existants , le plus général serait un NSError, et vous pouvez créer une méthode d'usine pour en créer et en lancer un avec un message personnalisé.

Arkku
la source
Salut, je sais que cela fait un an que vous avez publié cette réponse, mais j'aimerais savoir s'il est possible de pénétrer à l' Stringintérieur de votre errorMessage, si oui, comment puis-je faire cela?
Renan Camaforte
1
@RenanCamaforte Je suis désolé, je ne comprends pas la question? Le Stringest associé ici au MyError.RuntimeError(défini au moment de throw), et vous y accédez au catch(avec let errorMessage).
Arkku
1
On vous a demandé la solution la plus simple. La solution lorsque vous créez des énumérations, des fonctions, etc. personnalisées n'est pas simple. Je connais au moins un moyen mais je ne le
posterai
3
@VyachaslavGerchicov Si vous ne connaissez pas un moyen plus simple pour Swift, qui a également été spécifié dans la question, alors ce serait le moyen le plus simple, même si vous ne le considérez pas comme simple dans un contexte plus général qui inclurait Objective-C . (En outre, cette réponse est essentiellement une définition unique d'une ligne d'une énumération, la fonction et son appel sont un exemple d'utilisation, ne faisant pas partie de la solution.)
Arkku
1
@Otar Oui, mais ... vous parlez try!, ce qui n'est pas utilisé ici. En effet, vous ne pouvez même pas faire l'appel potentiellement lanceur sans une sorte de try. (Cette partie du code est également l'exemple d'utilisation, pas la solution réelle.)
Arkku
136

Le moyen le plus simple est de se Stringconformer à Error:

extension String: Error {}

Ensuite, vous pouvez simplement lancer une chaîne:

throw "Some Error"

Pour que la chaîne elle-même soit celle localizedStringde l'erreur, vous pouvez à la place étendre LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}
Nick Keets
la source
C'est intelligent, mais y a-t-il un moyen de faire en sorte que ce localizedDescriptionsoit la chaîne elle-même?
villapossu
1
De manière très élégante!
Vitaliy Gozhenko
1
Élégant en effet! Mais cela tombe en panne pour moi dans les cibles de test avec le message suivant Redundant conformance of 'String' to protocol 'Error':(
Alexander Borisenko
2
Pour une raison quelconque, cela ne fonctionne pas pour moi. Dit qu'il ne peut pas terminer l'opération lors de l'analyse error.localizedDescriptionaprès avoir lancé une chaîne.
Noah Allen du
1
Attention: cette extension m'a posé des problèmes avec les bibliothèques externes. Voici mon exemple . Ceci est possible pour toute bibliothèque tierce qui gère les erreurs; J'éviterais les extensions qui rendent String conforme à Error.
Bryan W. Wagner
20

La solution de @ nick-keets est la plus élégante, mais elle est tombée en panne pour moi dans la cible de test avec l'erreur de compilation suivante:

Redundant conformance of 'String' to protocol 'Error'

Voici une autre approche:

struct RuntimeError: Error {
    let message: String

    init(_ message: String) {
        self.message = message
    }

    public var localizedDescription: String {
        return message
    }
}

Et à utiliser:

throw RuntimeError("Error message.")
Alexandre Borisenko
la source
19

Vérifiez cette version cool. L'idée est d'implémenter les protocoles String et ErrorType et d'utiliser la valeur rawValue de l'erreur.

enum UserValidationError: String, Error {
  case noFirstNameProvided = "Please insert your first name."
  case noLastNameProvided = "Please insert your last name."
  case noAgeProvided = "Please insert your age."
  case noEmailProvided = "Please insert your email."
}

Usage:

do {
  try User.define(firstName,
                  lastName: lastName,
                  age: age,
                  email: email,
                  gender: gender,
                  location: location,
                  phone: phone)
}
catch let error as User.UserValidationError {
  print(error.rawValue)
  return
}
Teodor Ciuraru
la source
Il semble y avoir peu d'avantages dans cette approche, car vous avez toujours besoin du as User.UserValidationErroret en plus de .rawValue. Cependant, si vous implémentez à la place en CustomStringConvertibletant que var description: String { return rawValue }, il peut être utile d'obtenir les descriptions personnalisées à l'aide de la syntaxe enum sans avoir à passer par rawValuechaque endroit où vous l'imprimez.
Arkku
1
mieux implémenter la méthode localiséeDescription pour renvoyer .rawValue
DanSkeel
16

Swift 4:

Selon:

https://developer.apple.com/documentation/foundation/nserror

si vous ne souhaitez pas définir d'exception personnalisée, vous pouvez utiliser un objet NSError standard comme suit:

import Foundation

do {
  throw NSError(domain: "my error description", code: 42, userInfo: ["ui1":12, "ui2":"val2"] ) 
}
catch let error as NSError {
  print("Caught NSError: \(error.localizedDescription), \(error.domain), \(error.code)")
  let uis = error.userInfo 
  print("\tUser info:")
  for (key,value) in uis {
    print("\t\tkey=\(key), value=\(value)")
  }
}

Impressions:

Caught NSError: The operation could not be completed, my error description, 42
    User info:
        key=ui1, value=12
        key=ui2, value=val2

Cela vous permet de fournir une chaîne personnalisée, plus un code numérique et un dictionnaire avec toutes les données supplémentaires dont vous avez besoin, de tout type.

NB: cela a été testé sur OS = Linux (Ubuntu 16.04 LTS).

PJ_Finnegan
la source
12

Solution la plus simple sans extensions, énumérations, classes, etc. supplémentaires:

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()
Vyachaslav Gerchicov
la source
2
ré. vos commentaires sur ma réponse, ce n'est simple que dans le sens où vous avez décidé de manière quelque peu arbitraire que la définition et l'énumération ou l'extension une fois est compliquée. Donc, oui, votre réponse n'a aucune ligne de "configuration", mais au prix d'avoir chaque exception lancée soit un sort compliqué et non Swiftlike ( raise()au lieu de throw) difficile à retenir. Comparez votre solution avec throw Foo.Bar("baz")ou throw "foo"multipliez par le nombre d'endroits où une exception est levée - OMI, les frais uniques d'extension ou d'énumération d'une ligne sont de loin préférables à des choses comme NSExceptionName.
Arkku
@Arkku Par exemple postNotificationnécessite 2-3 paramètres et son sélecteur est similaire à celui-ci. Remplacez-vous Notificationet / ou NotificationCenterdans chaque projet pour lui permettre d'accepter moins de paramètres d'entrée?
Vyachaslav Gerchicov
1
Non, et je n'utiliserais même pas la solution dans ma propre réponse; Je l'ai seulement publié pour répondre à la question, pas parce que c'est quelque chose que je ferais moi-même. Quoi qu'il en soit, c'est d'ailleurs le point: je suis d'avis que votre réponse est de loin plus compliquée à utiliser que la mienne ou celle de Nick Keets. Bien sûr, il y a d'autres points valides à considérer, comme si étendre Stringpour se conformer à Errorest trop surprenant, ou si une MyErrorénumération est trop vague (personnellement, je répondrais oui aux deux, et à la place, je ferais un cas d'énumération séparé pour chaque erreur, c'est-à-dire, throw ThisTypeOfError.thisParticularCase).
Arkku
6

Sur la base de la réponse @Nick keets, voici un exemple plus complet:

extension String: Error {} // Enables you to throw a string

extension String: LocalizedError { // Adds error.localizedDescription to Error instances
    public var errorDescription: String? { return self }
}

func test(color: NSColor) throws{
    if color == .red {
        throw "I don't like red"
    }else if color == .green {
        throw "I'm not into green"
    }else {
        throw "I like all other colors"
    }
}

do {
    try test(color: .green)
} catch let error where error.localizedDescription == "I don't like red"{
    Swift.print ("Error: \(error)") // "I don't like red"
}catch let error {
    Swift.print ("Other cases: Error: \(error.localizedDescription)") // I like all other colors
}

Publié à l'origine sur mon blog swift: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

éoniste
la source
1
TBH: Je fais maintenant justethrow NSError(message: "err", code: 0)
eonist
Donc vous n'utilisez même pas votre propre exemple? : D Oh, et le premier argument devrait être domain, non message, non?
NRitH
1
Votre droit, domaine. Et non, ajoute trop de sucre dans le code. Je fais généralement beaucoup de petits frameworks et modules et j'essaie de garder le sucre d'extension pratique bas. Ces jours-ci, j'essaie d'utiliser un mélange entre Result et NSError
eonist
6

Dans le cas où vous n'avez pas besoin de détecter l'erreur et que vous souhaitez immédiatement arrêter l'application, vous pouvez utiliser une fatalError: fatalError ("Custom message here")

Roney Sampaio
la source
3
Notez que cela ne générera pas d'erreur qui peut être interceptée. Cela plantera l'application.
Adil Hussain
4

J'aime la réponse de @ Alexander-Borisenko, mais la description localisée n'a pas été renvoyée lorsqu'elle a été détectée comme une erreur. Il semble que vous deviez utiliser LocalizedError à la place:

struct RuntimeError: LocalizedError
{
    let message: String

    init(_ message: String)
    {
        self.message = message
    }

    public var errorDescription: String?
    {
        return message
    }
}

Voir cette réponse pour plus de détails.

Benjamin Smith
la source