Самый простой способ вызвать ошибку / исключение с помощью специального сообщения в Swift 2?

136

Я хочу сделать что-то в Swift 2, что я привык делать на нескольких других языках: генерировать исключение времени выполнения с пользовательским сообщением. Например (на Java):

throw new RuntimeException("A custom message here")

Я понимаю, что могу генерировать перечислимые типы, которые соответствуют протоколу ErrorType, но я не хочу определять перечисления для каждого типа ошибки, которую я генерирую. В идеале я хотел бы иметь возможность подражать приведенному выше примеру как можно ближе. Я посмотрел на создание собственного класса, который реализует протокол ErrorType, но я даже не могу понять, чего требует этот протокол (см. Документацию ). Идеи?

markdb314
источник
2
Swift 2 throw / catch не являются исключением.
Zaph

Ответы:

194

Самый простой подход, вероятно, состоит в том, чтобы определить один пользовательский элемент, к которому прикреплен enumтолько один :caseString

enum MyError: ErrorType {
    case runtimeError(String)
}

Или, как в Swift 4:

enum MyError: Error {
    case runtimeError(String)
}

Пример использования будет что-то вроде:

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

Если вы хотите использовать существующие Errorтипы, самый общий из них будет an NSError, и вы можете создать фабричный метод, чтобы создать и выдать его с пользовательским сообщением.

Arkku
источник
Привет, я знаю, что это был год, когда вы опубликовали этот ответ, но я хотел бы знать, возможно ли получить Stringвнутреннюю часть вашего errorMessage, если да, то как мне это сделать?
Ренан Камафорте
1
@RenanCamaforte Извините, я не понимаю вопроса? Здесь Stringассоциируется с MyError.RuntimeError(установлено во время throw), и вы получаете доступ к нему в catchlet errorMessage).
Арку
1
Вас попросили самое простое решение. Решение при создании пользовательских перечислений, функций и т. Д. Не простое. Я знаю хотя бы один способ, но я не буду публиковать его там, потому что он предназначен для объектива-C
Вячеслав Герчиков
3
@VyachaslavGerchicov Если вы не знаете более простой способ для Swift, который также был указан в вопросе, то это будет самый простой способ, даже если вы не считаете его простым в более общем контексте, который включает Objective-C , (Кроме того, этот ответ в основном является однострочным одноразовым определением перечисления, функция и ее вызов являются примером использования, а не частью решения.)
Arkku
1
@ Отар Да, но ... ты говоришь try!, что здесь не используется. Вы действительно не можете даже сделать потенциально бросающий вызов без какого-либо рода try. (Также эта часть кода является примером использования, а не фактическим решением.)
Arkku
136

Самый простой способ - это Stringсоответствовать Error:

extension String: Error {}

Тогда вы можете просто бросить строку:

throw "Some Error"

Чтобы сделать строку самой localizedStringошибкой, вы можете вместо этого расширить LocalizedError:

extension String: LocalizedError {
    public var errorDescription: String? { return self }
}
Ник Китс
источник
Это умно, но есть ли способ заставить его localizedDescriptionбыть самой строкой?
Villapossu
1
Очень элегантный способ!
Виталий Гоженко
1
Действительно элегантно! Но это ломается для меня в тестовых целях со следующим сообщением Redundant conformance of 'String' to protocol 'Error':(
Александр Борисенко
2
По некоторым причинам это не работает для меня. Говорит, что не может завершить операцию при разборе error.localizedDescriptionпосле броска строки.
Ноа Аллен
1
Предупреждение: это расширение вызвало у меня проблемы с внешними библиотеками. Вот мой пример . Это возможно для любой сторонней библиотеки, которая управляет ошибками; Я бы избегал расширений, которые заставляют String соответствовать Error.
Брайан В. Вагнер
20

Решение @ nick-keets является наиболее элегантным, но оно сломалось для меня в тестовой цели со следующей ошибкой времени компиляции:

Redundant conformance of 'String' to protocol 'Error'

Вот еще один подход:

struct RuntimeError: Error {
    let message: String

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

    public var localizedDescription: String {
        return message
    }
}

И использовать:

throw RuntimeError("Error message.")
Александр Борисенко
источник
19

Проверьте эту классную версию. Идея состоит в том, чтобы реализовать оба протокола String и ErrorType и использовать rawValue ошибки.

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

Использование:

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
}
Теодор Чурару
источник
Кажется, в этом подходе мало пользы, так как вам все еще нужны as User.UserValidationErrorи .rawValue. Однако, если вы вместо этого реализовали CustomStringConvertibleкак var description: String { return rawValue }, было бы полезно получить пользовательские описания с использованием синтаксиса enum без необходимости проходить через rawValueвсе места, где вы его печатаете.
Арку
1
лучше реализовать метод localizedDescription для возврата .rawValue
DanSkeel
16

Свифт 4:

Согласно:

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

если вы не хотите определять пользовательское исключение, вы можете использовать стандартный объект NSError следующим образом:

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

Печать:

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

Это позволяет вам предоставить пользовательскую строку, а также числовой код и словарь со всеми необходимыми дополнительными данными любого типа.

Примечание: это было протестировано на OS = Linux (Ubuntu 16.04 LTS).

PJ_Finnegan
источник
12

Самое простое решение без дополнительных расширений, перечислений, классов и т. Д .:

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()
Вячеслав Герчиков
источник
2
число рейнольдса Ваши комментарии к моему ответу просты только в том смысле, что вы несколько произвольно решили, что определение и перечисление или расширение один раз сложно. Итак, да, ваш ответ содержит ноль строк «настройки», но за счет того, что каждое выброшенное исключение будет сложным и не быстрым, как ( raise()вместо throw) заклинанием, которое трудно запомнить. Сравните ваше решение с throw Foo.Bar("baz")или throw "foo"умножьте на количество мест, где выдается исключение - IMO единовременная плата за однострочное расширение или enum гораздо предпочтительнее, чем подобные вещи NSExceptionName.
Арку
@Arkku Например, postNotificationтребуется 2-3 параметра и его селектор похож на этот. Вы переопределяете Notificationи / или NotificationCenterв каждом проекте, чтобы позволить ему принимать меньше входных параметров?
Вячеслав Герчиков,
1
Нет, и я бы даже не использовал решение в своем собственном ответе; Я только отправил это, чтобы ответить на вопрос, не потому что это - кое-что, что я сделал бы сам. В любом случае, это не главное: я придерживаюсь мнения, что ваш ответ гораздо сложнее использовать, чем мой или Ник Китс. Конечно, есть и другие важные моменты, которые следует учитывать, например, слишком ли удивляет расширение Stringсоответствия Error, или если MyErrorперечисление слишком расплывчато (лично я отвечал бы да обоим, и вместо этого делал бы отдельный случай перечисления для каждой ошибки, т. Е. throw ThisTypeOfError.thisParticularCase).
Арку
6

Основываясь на ответе @Nick keets, вот более полный пример:

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
}

Первоначально опубликовано в моем быстром блоге: http://eon.codes/blog/2017/09/01/throwing-simple-errors/

eonist
источник
1
TBH: я сейчас просто делаюthrow NSError(message: "err", code: 0)
eonist
Так вы даже не используете свой собственный пример? О, и первый аргумент должен быть domain, не так messageли?
NRitH
1
Ваше право, домен. И нет, добавляет в код слишком много сахара. Я обычно делаю много небольших фреймворков и модулей и стараюсь поддерживать удобное расширение на низком уровне. В эти дни я пытаюсь использовать смесь между Result и NSError
eonist
6

Если вам не нужно отлавливать ошибку и вы хотите немедленно остановить приложение, вы можете использовать fatalError: fatalError ("Custom message here")

Рони Сампайо
источник
3
Обратите внимание, что это не выдаст ошибку, которая может быть перехвачена. Это приведет к сбою приложения.
Адиль Хуссейн
4

Мне нравится ответ @ Alexander-Borisenko, но локализованное описание не было возвращено при обнаружении ошибки. Кажется, что вам нужно использовать LocalizedError вместо:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

Смотрите этот ответ для более подробной информации.

Бенджамин Смит
источник