A maneira mais simples de gerar um erro / exceção com uma mensagem personalizada no Swift 2?

136

Quero fazer algo no Swift 2 que estou acostumado a fazer em vários outros idiomas: lançar uma exceção de tempo de execução com uma mensagem personalizada. Por exemplo (em Java):

throw new RuntimeException("A custom message here")

Entendo que posso lançar tipos de enumerações que estejam em conformidade com o protocolo ErrorType, mas não quero definir enumerações para todos os tipos de erros lançados. Idealmente, eu gostaria de imitar o exemplo acima o mais próximo possível. Procurei criar uma classe personalizada que implementa o protocolo ErrorType, mas não consigo nem descobrir o que esse protocolo exige (consulte a documentação ). Ideias?

markdb314
fonte
2
Swift 2 throw / catch não são exceções.
Zaph 16/07/2015

Respostas:

194

A abordagem mais simples é provavelmente definir um costume enumcom apenas um caseque tenha um Stringanexo:

enum MyError: ErrorType {
    case runtimeError(String)
}

Ou, como no Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

Exemplo de uso seria algo como:

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

Se você deseja usar Errortipos existentes , o mais geral seria um NSErrore você pode criar um método de fábrica para criar e lançar um com uma mensagem personalizada.

Arkku
fonte
Olá, eu sei que já faz um ano que você postou esta resposta, mas eu gostaria de saber se é possível obter informações Stringinternas errorMessage, se sim, como faço isso?
Renan Camaforte
1
@RenanCamaforte Sinto muito, não entendi a pergunta? O Stringestá associado aqui com o MyError.RuntimeError(definido no momento de throw) e você obtém acesso a ele no catch(com let errorMessage).
Arkku
1
Você foi solicitado a solução mais simples. A solução ao criar enumerações, funções e etc personalizadas não é simples. Eu sei que pelo menos um caminho, mas eu não vou postá-lo lá porque é para objective-C
Vyachaslav Gerchicov
3
@VyachaslavGerchicov Se você não conhece uma maneira mais simples para Swift, que também foi especificada na pergunta, essa seria a maneira mais simples , mesmo que você não a considere simples em um contexto mais geral que incluiria o Objective-C . (Além disso, esta resposta é, basicamente, um uma linha de definição do tempo de um um enum, a função e o seu pedido é um exemplo de uso, que não faz parte da solução.)
Arkku
1
@ Otar Sim, mas ... você está falando try!, o que não é usado aqui. Você realmente não pode nem fazer a chamada potencialmente lançada sem algum tipo de try. (Também a parte do código é o uso de exemplo, e não a solução real.)
Arkku
136

A maneira mais simples é fazer a Stringconformidade com Error:

extension String: Error {}

Então você pode simplesmente jogar uma string:

throw "Some Error"

Para tornar a própria string localizedStringo erro, você pode estender LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}
Nick Keets
fonte
Isso é inteligente, mas existe uma maneira de fazer com que localizedDescriptionseja a própria string?
villapossu
1
Maneira muito elegante!
precisa saber é o seguinte
1
Elegante de fato! Mas ele se divide em alvos de teste com a seguinte mensagem Redundant conformance of 'String' to protocol 'Error':(
Alexander Borisenko 23/08
2
Por alguma razão, isso não funciona para mim. Diz que não pode concluir a operação ao analisar error.localizedDescriptiondepois de lançar uma string.
Noé Allen
1
Aviso: esta extensão me causou problemas com bibliotecas externas. Aqui está o meu exemplo . Isso é possível para qualquer biblioteca de terceiros que gerencia erros; Eu evitaria extensões que tornassem String em conformidade com Error.
Bryan W. Wagner
20

A solução do @ nick-keets é mais elegante, mas foi quebrada para mim no destino de teste com o seguinte erro de tempo de compilação:

Redundant conformance of 'String' to protocol 'Error'

Aqui está outra abordagem:

struct RuntimeError: Error {
    let message: String

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

    public var localizedDescription: String {
        return message
    }
}

E para usar:

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

Confira esta versão legal. A idéia é implementar os protocolos String e ErrorType e usar o rawValue do erro.

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
fonte
Parece haver pouco benefício nessa abordagem, pois você ainda precisa do as User.UserValidationErrore acima disso o .rawValue. No entanto, se você implementou CustomStringConvertiblecomo var description: String { return rawValue }, pode ser útil obter as descrições personalizadas usando a sintaxe da enum sem precisar passar por rawValuetodos os locais em que você a imprime.
Arkku 18/10/19
1
melhor implementar o método localizedDescription para retornar .rawValue
DanSkeel
16

Swift 4:

Conforme:

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

se você não quiser definir uma exceção personalizada, poderá usar um objeto NSError padrão da seguinte maneira:

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

Impressões:

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

Isso permite que você forneça uma sequência personalizada, além de um código numérico e um dicionário com todos os dados adicionais necessários, de qualquer tipo.

Nota: foi testado no SO = Linux (Ubuntu 16.04 LTS).

PJ_Finnegan
fonte
12

Solução mais simples, sem extensões extras, enumerações, classes e etc .:

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()
Vyachaslav Gerchicov
fonte
2
ré. seus comentários sobre a minha resposta, isso é simples apenas no sentido de que você tenha um pouco arbitrariamente decidiu que a definição e enumeração ou a extensão uma vez é complicado. Portanto, sim, sua resposta tem zero linhas de "configuração", mas ao custo de ter todas as exceções lançadas, seja um feitiço complicado e não do tipo Swift (em raise()vez de throw) que é difícil de lembrar. Compare sua solução com throw Foo.Bar("baz")ou throw "foo"multiplique pelo número de lugares em que uma exceção é lançada - na IMO, a taxa única de extensão ou enum de uma linha é muito preferível a coisas assim NSExceptionName.
Arkku
@Arkku Por exemplo, postNotificationrequer 2-3 parâmetros e seu seletor é semelhante a este. Você substitui Notificatione / ou NotificationCenterem cada projeto para permitir que ele aceite menos parâmetros de entrada?
Vyachaslav Gerchicov
1
Não, e eu nem usaria a solução em minha própria resposta; Eu apenas o publiquei para responder à pergunta, não porque é algo que eu mesmo faria. De qualquer forma, isso está além do ponto: eu mantenho a opinião de que sua resposta é muito mais complicada de usar do que a minha ou a de Nick Keets. É claro que existem outros pontos válidos a serem considerados, como se estender Stringpara se conformar Erroré muito surpreendente ou se um MyErrorenum é muito vago (pessoalmente, eu responderia sim a ambos e, em vez disso, faria um caso enum separado para cada erro, ou seja, throw ThisTypeOfError.thisParticularCase)
Arkku
6

Com base na resposta de @Nick keets, aqui está um exemplo mais 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 no meu blog rápido: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

eonista
fonte
1
TBH: Agora eu apenas façothrow NSError(message: "err", code: 0)
eonist
Então você nem usa seu próprio exemplo? : D Ah, e o primeiro argumento deve ser domain, não message, certo?
NRitH 25/02
1
Você está certo, domínio. E não, adiciona muito açúcar no código. Eu costumo fazer um monte de pequenas estruturas e módulos e tento manter o açúcar de extensão conveniente baixo. Hoje em dia eu tento usar uma mistura entre Result e NSError
eonist
6

Caso você não precise capturar o erro e queira parar imediatamente o aplicativo, você pode usar um fatalError: fatalError ("Custom message here")

Roney Sampaio
fonte
3
Observe que isso não gera um erro que pode ser detectado. Isso irá travar o aplicativo.
Adil Hussain
4

Gosto da resposta de @ Alexander-Borisenko, mas a descrição localizada não foi retornada quando detectada como um erro. Parece que você precisa usar LocalizedError:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

Veja esta resposta para mais detalhes.

Benjamin Smith
fonte