Einfachste Möglichkeit, einen Fehler / eine Ausnahme mit einer benutzerdefinierten Nachricht in Swift 2 auszulösen?

136

Ich möchte in Swift 2 etwas tun, das ich in mehreren anderen Sprachen gewohnt bin: eine Laufzeitausnahme mit einer benutzerdefinierten Nachricht auslösen. Zum Beispiel (in Java):

throw new RuntimeException("A custom message here")

Ich verstehe, dass ich Aufzählungstypen auslösen kann, die dem ErrorType-Protokoll entsprechen, aber ich möchte nicht für jeden Fehlertyp Aufzählungen definieren müssen. Im Idealfall möchte ich das obige Beispiel so genau wie möglich nachahmen können. Ich habe versucht, eine benutzerdefinierte Klasse zu erstellen, die das ErrorType-Protokoll implementiert, aber ich kann nicht einmal herausfinden, was dieses Protokoll erfordert (siehe Dokumentation ). Ideen?

markdb314
quelle
2
Swift 2 Throw / Catch sind keine Ausnahmen.
Zaph

Antworten:

194

Der einfachste Ansatz ist wahrscheinlich zu definieren , eine benutzerdefinierte enummit nur einer case, der eine hat Stringan sie:

enum MyError: ErrorType {
    case runtimeError(String)
}

Oder ab Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

Beispiel Verwendung wäre so etwas wie:

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

Wenn Sie vorhandene ErrorTypen verwenden möchten , ist der allgemeinste ein NSError, und Sie können eine Factory-Methode erstellen, um einen mit einer benutzerdefinierten Nachricht zu erstellen und zu werfen.

Arkku
quelle
Hallo, ich weiß, es ist ein Jahr her, dass Sie diese Antwort gepostet haben, aber ich würde gerne wissen, ob es möglich ist, Stringin Ihr Inneres zu gelangen errorMessage. Wenn ja, wie mache ich das?
Renan Camaforte
1
@ RenanCamaforte Es tut mir leid, ich verstehe die Frage nicht? Das Stringist hier mit dem MyError.RuntimeError(zum Zeitpunkt von throw) festgelegten verknüpft , und Sie erhalten Zugriff auf das catch(mit let errorMessage).
Arkku
1
Sie wurden nach der einfachsten Lösung gefragt. Die Lösung beim Erstellen von benutzerdefinierten Aufzählungen, Funktionen usw. ist nicht einfach. Ich kenne mindestens einen Weg, aber ich werde ihn dort nicht posten, weil er für Ziel-C ist
Vyachaslav Gerchicov
3
@VyachaslavGerchicov Wenn Sie keinen einfacheren Weg für Swift kennen, der auch in der Frage angegeben wurde, dann wäre dies der einfachste Weg, selbst wenn Sie ihn in einem allgemeineren Kontext, der Objective-C enthalten würde, nicht für einfach halten . (Auch diese Antwort ist im Grunde eine einzeilige einmalige Definition einer Aufzählung, die Funktion und ihr Aufruf sind ein Beispiel für die Verwendung, nicht Teil der Lösung.)
Arkku
1
@ Otar Ja, aber ... du redest try!, was hier nicht verwendet wird. Sie können in der Tat nicht einmal den potenziell werfenden Anruf ohne irgendeine Art von tätigen try. (Auch dieser Teil des Codes ist die Beispielverwendung, nicht die eigentliche Lösung.)
Arkku
136

Der einfachste Weg ist, sich Stringanzupassen an Error:

extension String: Error {}

Dann können Sie einfach eine Zeichenfolge werfen:

throw "Some Error"

Um die Zeichenfolge selbst localizedStringzum Fehler zu machen, können Sie stattdessen Folgendes erweitern LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}
Nick Keets
quelle
Das ist klug, aber gibt es eine Möglichkeit, localizedDescriptiones zur Saite selbst zu machen ?
Villapossu
1
Sehr elegante Art!
Vitaliy Gozhenko
1
Elegant in der Tat! Aber es bricht für mich in Testzielen mit der folgenden Meldung zusammen Redundant conformance of 'String' to protocol 'Error':(
Alexander Borisenko
2
Aus irgendeinem Grund funktioniert das bei mir nicht. Sagt, dass der Vorgang beim Parsen error.localizedDescriptionnach dem Werfen eines Strings nicht abgeschlossen werden kann .
Noah Allen
1
Warnung: Diese Erweiterung hat mir Probleme mit externen Bibliotheken verursacht. Hier ist mein Beispiel . Dies ist für jede Bibliothek eines Drittanbieters möglich, die Fehler verwaltet. Ich würde Erweiterungen vermeiden, die String an Fehler anpassen.
Bryan W. Wagner
20

Die Lösung von @ nick-keets ist am elegantesten, aber im Testziel ist sie mit dem folgenden Fehler bei der Kompilierung zusammengebrochen:

Redundant conformance of 'String' to protocol 'Error'

Hier ist ein anderer Ansatz:

struct RuntimeError: Error {
    let message: String

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

    public var localizedDescription: String {
        return message
    }
}

Und zu verwenden:

throw RuntimeError("Error message.")
Alexander Borisenko
quelle
19

Schauen Sie sich diese coole Version an. Die Idee ist, sowohl String- als auch ErrorType-Protokolle zu implementieren und den rawValue des Fehlers zu verwenden.

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."
}

Verwendung:

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
quelle
Dieser Ansatz scheint wenig Nutzen zu haben, da Sie immer noch das as User.UserValidationErrorund darüber hinaus das benötigen .rawValue. Wenn Sie jedoch stattdessen CustomStringConvertibleals implementiert haben var description: String { return rawValue }, kann es hilfreich sein, die benutzerdefinierten Beschreibungen mithilfe der Enum-Syntax abzurufen, ohne sie an rawValuejeder Stelle durchgehen zu müssen, an der Sie sie drucken.
Arkku
1
Implementieren Sie besser die localizedDescription-Methode, um .rawValue
DanSkeel
16

Swift 4:

Wie folgt:

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

Wenn Sie keine benutzerdefinierte Ausnahme definieren möchten, können Sie ein Standard-NSError-Objekt wie folgt verwenden:

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)")
  }
}

Drucke:

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

Auf diese Weise können Sie eine benutzerdefinierte Zeichenfolge sowie einen numerischen Code und ein Wörterbuch mit allen zusätzlichen Daten eines beliebigen Typs bereitstellen.

NB: Dies wurde unter OS = Linux (Ubuntu 16.04 LTS) getestet.

PJ_Finnegan
quelle
12

Einfachste Lösung ohne zusätzliche Erweiterungen, Aufzählungen, Klassen usw.

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()
Vyachaslav Gerchicov
quelle
2
Re. Ihre Kommentare zu meiner Antwort, dies ist nur in dem Sinne einfach, dass Sie etwas willkürlich entschieden haben, dass das Definieren und Aufzählen oder Erweitern einmal kompliziert ist. Also, ja, Ihre Antwort enthält null Zeilen "Setup", aber auf Kosten jeder ausgelösten Ausnahme ist dies ein komplizierter und nicht schneller ( raise()statt throw) Zauber, der schwer zu merken ist. Vergleichen Sie Ihre Lösung mit throw Foo.Bar("baz")oder throw "foo"multiplizieren Sie sie mit der Anzahl der Stellen, an denen eine Ausnahme ausgelöst wird. IMO ist die einmalige Gebühr für eine einzeilige Nebenstelle oder Aufzählung weitaus besser als solche NSExceptionName.
Arkku
@Arkku Benötigt zum Beispiel postNotification2-3 Parameter und sein Selektor ähnelt diesem. Überschreiben Sie Notificationund / oder NotificationCenterin jedem Projekt, damit es weniger Eingabeparameter akzeptiert?
Vyachaslav Gerchicov
1
Nein, und ich würde die Lösung nicht einmal in meiner eigenen Antwort verwenden. Ich habe es nur gepostet, um die Frage zu beantworten, nicht weil ich es selbst tun würde. Jedenfalls ist das nicht der Punkt: Ich stehe zu der Meinung, dass Ihre Antwort weitaus komplizierter zu verwenden ist als meine oder die von Nick Keets. Natürlich gibt es andere gültige Punkte, die berücksichtigt werden müssen, z. B. ob es zu überraschend ist, sich Stringzu erweitern , um sich anzupassen Error, oder ob eine MyErrorAufzählung zu vage ist (persönlich würde ich auf beide mit Ja antworten und stattdessen für jeden Fehler einen separaten Aufzählungsfall erstellen, d. H. throw ThisTypeOfError.thisParticularCase).
Arkku
6

Basierend auf der Antwort von @Nick keets ist hier ein vollständigeres Beispiel:

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
}

Ursprünglich auf meinem schnellen Blog veröffentlicht: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

Eonist
quelle
1
TBH: Ich mache es jetzt einfachthrow NSError(message: "err", code: 0)
eonist
Sie verwenden also nicht einmal Ihr eigenes Beispiel? : D Oh, und das erste Argument sollte domainnicht messagerichtig sein?
NRitH
1
Dein Recht, Domain. Und nein, fügt dem Code zu viel Zucker hinzu. Normalerweise mache ich viele kleine Frameworks und Module und versuche, den praktischen Erweiterungszucker niedrig zu halten. In diesen Tagen versuche ich, eine Mischung zwischen Result und NSError zu verwenden
eonist
6

Falls Sie den Fehler nicht abfangen müssen und die Anwendung sofort stoppen möchten, können Sie einen fatalError verwenden: fatalError ("Custom message here")

Roney Sampaio
quelle
3
Beachten Sie, dass dies keinen Fehler auslöst, der abgefangen werden kann. Dadurch wird die App zum Absturz gebracht.
Adil Hussain
4

Ich mag die Antwort von @ Alexander-Borisenko, aber die lokalisierte Beschreibung wurde nicht zurückgegeben, wenn sie als Fehler abgefangen wurde. Es scheint, dass Sie stattdessen LocalizedError verwenden müssen:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

Siehe diese Antwort für weitere Details.

Benjamin Smith
quelle