Najprostszy sposób na zgłoszenie błędu / wyjątku z niestandardową wiadomością w Swift 2?

136

Chcę zrobić coś w Swift 2, do czego przywykłem w wielu innych językach: wyrzucić wyjątek środowiska wykonawczego z niestandardową wiadomością. Na przykład (w Javie):

throw new RuntimeException("A custom message here")

Rozumiem, że mogę zgłaszać typy wyliczeń zgodne z protokołem ErrorType, ale nie chcę definiować wyliczeń dla każdego typu zgłaszanego błędu. Idealnie, chciałbym móc jak najdokładniej naśladować powyższy przykład. Przyjrzałem się stworzeniu niestandardowej klasy, która implementuje protokół ErrorType, ale nie mogę nawet dowiedzieć się, czego wymaga ten protokół (zobacz dokumentację ). Pomysły?

markdb314
źródło
2
Swift 2 rzut / złapanie nie jest wyjątkiem.
zaph

Odpowiedzi:

194

Najprostszym podejściem jest prawdopodobnie zdefiniowanie jednego zwyczaju za enumpomocą tylko jednego, do casektórego jest Stringdołączony:

enum MyError: ErrorType {
    case runtimeError(String)
}

Lub od Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

Przykładowe użycie wyglądałoby tak:

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

Jeśli chcesz użyć istniejących Errortypów, najbardziej ogólnym z nich będzie an NSError, i możesz utworzyć metodę fabryczną, aby utworzyć i wyrzucić typ z niestandardową wiadomością.

Arkku
źródło
Cześć, wiem, że minął rok, kiedy opublikowałeś tę odpowiedź, ale chciałbym wiedzieć, czy jest możliwe, aby dostać się do Stringśrodka errorMessage, jeśli tak, jak mam to zrobić?
Renan Camaforte
1
@RenanCamaforte Przepraszam, nie rozumiem pytania? StringKojarzy się tutaj z MyError.RuntimeError(ustawiony na czasie throw), a zyskasz dostęp do go w catch(z let errorMessage).
Arkku
1
Poproszono Cię o najprostsze rozwiązanie. Rozwiązanie w przypadku tworzenia niestandardowych wyliczeń, funkcji itp. Nie jest proste. Znam co najmniej jedną drogę, ale nie będę pisać go tam, bo jest to dla Objective-C
Vyachaslav Gerchicov
3
@VyachaslavGerchicov Jeśli nie znasz prostszego sposobu na Swift, który również został określony w pytaniu, to byłby to najprostszy sposób, nawet jeśli nie uważasz, że jest to proste w bardziej ogólnym kontekście, który obejmowałby Objective-C . (Również ta odpowiedź jest w zasadzie jednowierszową, jednorazową definicją wyliczenia, funkcja i jej wywołanie są przykładem użycia, a nie częścią rozwiązania.)
Arkku
1
@Otar Tak, ale… mówisz o tym try!, co nie jest tutaj używane. Rzeczywiście nie możesz nawet wykonać potencjalnie rzucającego sprawdzenia bez jakiegoś rodzaju try. (Również ta część kodu jest przykładowym użyciem, a nie rzeczywistym rozwiązaniem).
Arkku
136

Najprostszym sposobem jest Stringdostosowanie się do Error:

extension String: Error {}

Następnie możesz po prostu rzucić ciąg:

throw "Some Error"

Aby sam ciąg był localizedStringbłędem, możesz zamiast tego rozszerzyć LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}
Nick Keets
źródło
To sprytne, ale czy istnieje sposób, aby uczynić localizedDescriptiongo samą struną?
villapossu
1
Bardzo elegancki sposób!
Vitaliy Gozhenko,
1
Rzeczywiście eleganckie! Ale psuje się dla mnie w celach testowych z następującym komunikatem Redundant conformance of 'String' to protocol 'Error':(
Alexander Borisenko,
2
Z jakiegoś powodu to nie działa dla mnie. Mówi, że nie może zakończyć operacji podczas analizowania error.localizedDescriptionpo rzuceniu ciągu.
Noah Allen
1
Ostrzeżenie: to rozszerzenie spowodowało dla mnie problemy z zewnętrznymi bibliotekami. Oto mój przykład . Jest to możliwe w przypadku dowolnej biblioteki innej firmy, która zarządza błędami; Unikałbym rozszerzeń, które powodują, że String jest zgodny z Error.
Bryan W. Wagner
20

Rozwiązanie @ nick-keets jest najbardziej eleganckie, ale załamało się dla mnie w celu testowym z następującym błędem czasu kompilacji:

Redundant conformance of 'String' to protocol 'Error'

Oto inne podejście:

struct RuntimeError: Error {
    let message: String

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

    public var localizedDescription: String {
        return message
    }
}

I do użycia:

throw RuntimeError("Error message.")
Alexander Borisenko
źródło
19

Sprawdź tę fajną wersję. Chodzi o to, aby zaimplementować protokoły String i ErrorType i użyć rawValue błędu.

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

Stosowanie:

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
źródło
Wydaje się, że takie podejście przynosi niewielkie korzyści, ponieważ nadal potrzebujesz as User.UserValidationErrorpakietu .rawValue. Jeśli jednak zamiast tego zaimplementowano CustomStringConvertiblejako var description: String { return rawValue }, przydatne może być uzyskanie niestandardowych opisów przy użyciu składni wyliczenia bez konieczności przechodzenia przez rawValuekażde miejsce, w którym je drukujesz.
Arkku
1
lepiej zaimplementuj metodę localizedDescription, aby zwrócić .rawValue
DanSkeel
16

Swift 4:

Jak na:

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

jeśli nie chcesz definiować niestandardowego wyjątku, możesz użyć standardowego obiektu NSError w następujący sposób:

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

Wydruki:

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

Umożliwia to dostarczenie niestandardowego ciągu, a także kodu numerycznego i słownika ze wszystkimi dodatkowymi danymi, których potrzebujesz, dowolnego typu.

Uwaga: zostało to przetestowane na systemie OS = Linux (Ubuntu 16.04 LTS).

PJ_Finnegan
źródło
12

Najprostsze rozwiązanie bez dodatkowych rozszerzeń, wyliczeń, klas itp .:

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()
Vyachaslav Gerchicov
źródło
2
re. z uwagami na temat mojej odpowiedzi, jest to proste tylko w tym sensie, że nieco arbitralnie zdecydowałeś, że zdefiniowanie i wyliczenie lub rozszerzenie raz jest skomplikowane. Więc tak, twoja odpowiedź ma zero wierszy „konfiguracji”, ale kosztem tego, że każdy rzucony wyjątek jest skomplikowanym i nie podobnym do Swifta ( raise()zamiast throw) zaklęciem, które jest trudne do zapamiętania. Porównaj swoje rozwiązanie z throw Foo.Bar("baz")lub throw "foo"pomnożone przez liczbę miejsc, w których zgłaszany jest wyjątek - IMO jednorazowa opłata za przedłużenie jednowierszowe lub wyliczenie jest znacznie lepsza niż rzeczy takie jak NSExceptionName.
Arkku,
@Arkku Na przykład postNotificationwymaga 2-3 paramów i jego selektor jest podobny do tego. Czy nadpisujesz Notificationi / lub NotificationCenterw każdym projekcie, aby umożliwić akceptację mniejszej liczby parametrów wejściowych?
Vyachaslav Gerchicov
1
Nie, i nawet nie użyłbym tego rozwiązania we własnej odpowiedzi; Wysłałem go tylko po to, aby odpowiedzieć na pytanie, a nie dlatego, że zrobię to sam. W każdym razie nie ma to znaczenia: podtrzymuję opinię, że twoja odpowiedź jest o wiele bardziej skomplikowana w użyciu niż moja lub Nicka Keetsa. Oczywiście istnieją inne ważne kwestie do rozważenia, na przykład czy rozszerzenie w Stringcelu uzyskania zgodności Errorjest zbyt zaskakujące lub jeśli MyErrorwyliczenie jest zbyt niejasne (osobiście odpowiedziałbym twierdząco na oba i zamiast tego zrobiłbym osobny przypadek wyliczenia dla każdego błędu, tj. throw ThisTypeOfError.thisParticularCase).
Arkku,
6

Na podstawie odpowiedzi @Nick keets, oto bardziej kompletny przykład:

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
}

Pierwotnie opublikowane na moim swift blogu: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

eonista
źródło
1
TBH: Teraz po prostu to robięthrow NSError(message: "err", code: 0)
eonista
Więc nawet nie używasz własnego przykładu? : D Och, a pierwszym argumentem powinno być domain, nie message, prawda?
NRitH
1
Twoje prawo, domena. I nie, dodaje zbyt dużo cukru w ​​kodzie. Zwykle tworzę wiele małych frameworków i modułów i staram się utrzymać niski poziom cukru w ​​wygodnych rozszerzeniach. W dzisiejszych czasach staram się wykorzystać połączenie Result i NSError
eonist
6

W przypadku, gdy nie musisz łapać błędu i chcesz natychmiast zatrzymać aplikację, możesz użyć fatalError: fatalError ("Custom message here")

Roney Sampaio
źródło
3
Zauważ, że nie spowoduje to błędu, który można złapać. Spowoduje to awarię aplikacji.
Adil Hussain
4

Podoba mi się odpowiedź @ Alexander-Borisenko, ale zlokalizowany opis nie został zwrócony, gdy został złapany jako błąd. Wygląda na to, że zamiast tego musisz użyć LocalizedError:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

Zobacz tę odpowiedź, aby uzyskać więcej informacji.

Benjamin Smith
źródło