¿La forma más sencilla de lanzar un error / excepción con un mensaje personalizado en Swift 2?

136

Quiero hacer algo en Swift 2 que estoy acostumbrado a hacer en varios otros idiomas: lanzar una excepción de tiempo de ejecución con un mensaje personalizado. Por ejemplo (en Java):

throw new RuntimeException("A custom message here")

Entiendo que puedo lanzar tipos de enumeración que se ajusten al protocolo ErrorType, pero no quiero tener que definir enumeraciones para cada tipo de error que arroje. Idealmente, me gustaría poder imitar el ejemplo anterior lo más fielmente posible. Busqué crear una clase personalizada que implemente el protocolo ErrorType, pero ni siquiera puedo darme cuenta de lo que ese protocolo requiere (ver documentación ). Ideas?

markdb314
fuente
2
Swift 2 throw / catch no son excepciones.
zaph

Respuestas:

194

El enfoque más simple es probablemente definir una costumbre enumcon solo una caseque tenga un Stringadjunto:

enum MyError: ErrorType {
    case runtimeError(String)
}

O, a partir de Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

El uso de ejemplo sería algo como:

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

Si desea utilizar los Errortipos existentes , el más general sería un NSError, y podría hacer un método de fábrica para crear y lanzar uno con un mensaje personalizado.

Arkku
fuente
Hola, sé que ha pasado un año en que publicaste esta respuesta, pero me gustaría saber si es posible obtener información sobre Stringtu errorMessage, si es así, ¿cómo hago eso?
Renan Camaforte
1
@RenanCamaforte Lo siento, no entiendo la pregunta. El Stringestá asociado aquí con el MyError.RuntimeError(establecido en el momento de throw), y usted obtiene acceso a él en el catch(con let errorMessage).
Arkku
1
Le pidieron la solución más simple. La solución cuando crea enumeraciones, funciones, etc. personalizadas no es simple. Conozco al menos una forma, pero no la publicaré allí porque es para el objetivo C
Vyachaslav Gerchicov
3
@VyachaslavGerchicov Si no conoce una forma más simple para Swift, que también se especificó en la pregunta, entonces esta sería la forma más simple , incluso si no lo considera simple en un contexto más general que incluiría Objective-C . (Además, esta respuesta es, básicamente, una vez una definición de una línea de una enumeración, la función y su llamada es un ejemplo de uso, no es parte de la solución.)
Arkku
1
@Otar Sí, pero ... estás hablando try!, que no se usa aquí. De hecho, ni siquiera puede hacer la llamada potencialmente lanzada sin algún tipo de try. (También esa parte del código es el uso de ejemplo, no la solución real.)
Arkku
136

La forma más simple es hacer que se Stringconforme a Error:

extension String: Error {}

Entonces puedes tirar una cuerda:

throw "Some Error"

Para hacer que la cadena sea localizedStringel error, puede extenderla LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}
Nick Keets
fuente
Esto es inteligente, pero ¿hay alguna manera de hacer que localizedDescriptionsea ​​la cadena en sí?
villapossu
1
Muy elegante!
Vitaliy Gozhenko
1
Elegante por cierto! Pero se descompone para mí en objetivos de prueba con el siguiente mensaje Redundant conformance of 'String' to protocol 'Error':(
Alexander Borisenko
2
Por alguna razón, esto no funciona para mí. Dice que no puede completar la operación al analizar error.localizedDescriptiondespués de lanzar una cadena.
Noah Allen
1
Advertencia: esta extensión me causó problemas con las bibliotecas externas. Aquí está mi ejemplo . Esto es posible para cualquier biblioteca de terceros que gestione errores; Evitaría extensiones que hagan que String se ajuste al error.
Bryan W. Wagner
20

La solución de @ nick-keets es muy elegante, pero se desglosó en el objetivo de prueba con el siguiente error de tiempo de compilación:

Redundant conformance of 'String' to protocol 'Error'

Aquí hay otro enfoque:

struct RuntimeError: Error {
    let message: String

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

    public var localizedDescription: String {
        return message
    }
}

Y para usar:

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

Mira esta genial versión. La idea es implementar los protocolos String y ErrorType y usar el rawValue del error.

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

Uso:

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
fuente
Parece haber pocos beneficios en este enfoque, ya que todavía necesita el as User.UserValidationErrory además de eso .rawValue. Sin embargo, si en su lugar lo implementó CustomStringConvertiblecomo var description: String { return rawValue }, podría ser útil obtener las descripciones personalizadas utilizando la sintaxis de enumeración sin tener que pasar por rawValuetodos los lugares donde lo imprime.
Arkku
1
implementar mejor el método localizedDescription para devolver .rawValue
DanSkeel
16

Swift 4:

Según:

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

Si no desea definir una excepción personalizada, puede utilizar un objeto NSError estándar de la siguiente manera:

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

Huellas dactilares:

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

Esto le permite proporcionar una cadena personalizada, más un código numérico y un diccionario con todos los datos adicionales que necesita, de cualquier tipo.

NB: esto fue probado en OS = Linux (Ubuntu 16.04 LTS).

PJ_Finnegan
fuente
12

La solución más simple sin extensiones adicionales, enumeraciones, clases, etc.

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()
Vyachaslav Gerchicov
fuente
2
re. sus comentarios sobre mi respuesta, esto es simple solo en el sentido de que de alguna manera ha decidido arbitrariamente que definir y enumerar o ampliar una vez es complicado. Entonces, sí, su respuesta tiene cero líneas de "configuración", pero a costa de que cada excepción lanzada sea un hechizo complicado y no similar a Swift (en raise()lugar de throw) que es difícil de recordar. Compare su solución throw Foo.Bar("baz")o throw "foo"multiplíquela por el número de lugares donde se produce una excepción: en mi opinión, la tarifa única de extensión o enumeración de una línea es mucho mejor que cosas como esta NSExceptionName.
Arkku
@Arkku Por ejemplo, postNotificationrequiere 2-3 parámetros y su selector es similar a este. ¿Anula Notificationy / o NotificationCenteren cada proyecto para permitirle aceptar menos parámetros de entrada?
Vyachaslav Gerchicov
1
No, y ni siquiera usaría la solución en mi propia respuesta; Solo lo publiqué para responder la pregunta, no porque sea algo que haría yo mismo. De todos modos, eso no viene al caso: mantengo la opinión de que su respuesta es mucho más complicada de usar que la mía o la de Nick Keets. Por supuesto, hay otros puntos válidos a tener en cuenta, como si extender demasiado Stringpara conformarse Errores demasiado sorprendente o si una MyErrorenumeración es demasiado vaga (personalmente respondería sí a ambas, y en su lugar haría un caso de enumeración separado para cada error, es decir, throw ThisTypeOfError.thisParticularCase)
Arkku
6

Basado en la respuesta de @Nick keets, aquí hay un ejemplo más completo:

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
}

Publicado originalmente en mi rápido blog: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

eonista
fuente
1
TBH: ahora lo hagothrow NSError(message: "err", code: 0)
eonista
¿Entonces ni siquiera usas tu propio ejemplo? : D Oh, y el primer argumento debería ser domain, no message, ¿verdad?
NRitH
1
Tu derecho, dominio. Y no, agrega demasiada azúcar en el código. Por lo general, hago muchos marcos y módulos pequeños y trato de mantener baja la extensión de azúcar conveniente. En estos días trato de usar una mezcla entre Result y NSError
eonist
6

En caso de que no necesite detectar el error y desee detener inmediatamente la aplicación, puede usar un error fatal: fatalError ("Custom message here")

Roney Sampaio
fuente
3
Tenga en cuenta que esto no arrojará un error que pueda ser detectado. Esto bloqueará la aplicación.
Adil Hussain
4

Me gusta la respuesta de @ Alexander-Borisenko, pero la descripción localizada no fue devuelta cuando se detectó como un Error. Parece que necesita usar LocalizedError en su lugar:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

Vea esta respuesta para más detalles.

Benjamin Smith
fuente