Найпростіший спосіб викинути помилку / виняток за допомогою спеціального повідомлення у Swift 2?


136

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

throw new RuntimeException("A custom message here")

Я розумію, що я можу викидати типи перерахунків, які відповідають протоколу ErrorType, але мені не хочеться визначати переліки для кожного типу помилок, які я викидаю. В ідеалі я хотів би, щоб я міг імітувати приклад вище, наскільки це можливо. Я розглядав створення спеціального класу, який реалізує протокол ErrorType, але я навіть не можу зрозуміти, що потрібно для цього протоколу (див. Документацію ). Ідеї?


2
Swift 2 кидок / улов - не виняток.
zaph

Відповіді:


194

Найпростіший підхід - це, мабуть, визначити один звичай enumлише з одним case, який Stringдодається до нього:

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, і ви можете скористатися фабричним методом, щоб створити та викинути його за допомогою спеціального повідомлення.


Привіт, я знаю, що минув рік, коли ви опублікували цю відповідь, але я хотів би знати, чи можна потрапити Stringвсередину вашого errorMessage, якщо так, як це зробити?
Ренан Камафорте

1
@RenanCamaforte Вибачте, я не розумію питання? Тут Stringасоціюється з MyError.RuntimeError(встановлено на час throw), і ви отримуєте доступ до нього в catchlet errorMessage).
Арку

1
Вас попросили найпростішого рішення. Рішення, коли створюються спеціальні переліки, функції та інше, не просте. Я знаю принаймні один спосіб, але не публікую його там, тому що це для об'єктивних С
В’ячаслав Герчичев

3
@VyachaslavGerchicov Якщо ви не знаєте більш простого способу для Swift, який також був визначений у запитанні, то це був би найпростіший спосіб, навіть ви не вважаєте його простим у більш загальному контексті, який би включав Objective-C . (Крім того, ця відповідь є в основному
однорядним

1
@Otar Так, але ... ви говорите про це try!, яке тут не використовується. Ви дійсно навіть не можете здійснити потенційний кидаючи дзвінок без якогось try. (Також ця частина коду є прикладом використання, а не власне рішення.)
Арку

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

Це дозволяє надати спеціальний рядок, а також числовий код та словник з усіма необхідними додатковими даними будь-якого типу.

Примітка: це було протестовано на ОС = Linux (Ubuntu 16.04 LTS).


12

Найпростіше рішення без додаткових розширень, перерахунків, класів тощо:

NSException(name:NSExceptionName(rawValue: "name"), reason:"reason", userInfo:nil).raise()

2
повторно. ваші коментарі до моєї відповіді, це просто лише в тому сенсі, що ви дещо довільно вирішили, що визначити і перерахувати, або продовжити один раз складне. Так, так, у вашій відповіді є нульові рядки "налаштування", але ціною того, щоб кожен викинутий виняток бути складним і нескоростим ( raise()замість throw) заклинанням, яке важко запам'ятати. Порівняйте своє рішення з throw Foo.Bar("baz")чи throw "foo"помножено на кількість місць, де викинуто виняток - IMO одноразовий збір за однорядне розширення чи перерахунок набагато кращий для таких речей NSExceptionName.
Арку

@Arkku Наприклад, postNotificationпотрібно 2-3 парами, і його селектор схожий на цей. Ви переосмислюєте Notificationта / або NotificationCenterв кожному проекті, щоб дозволити йому приймати менше вхідних параметрів?
В’ячаслав Герчиков

1
Ні, і я б навіть не використовував рішення у власній відповіді; Я лише розмістив його, щоб відповісти на питання, а не тому, що це я би робив сам. У всякому разі, це крім пункту: я стою на думці, що вашу відповідь набагато складніше використовувати, ніж мою чи Ніка Кітса. Звичайно, слід врахувати й інші достовірні моменти, наприклад, якщо надсилання Stringна відповідність Errorзанадто дивно, або якщо MyErrorперерахунок занадто розпливчастий (особисто я відповів би так на обидва, а натомість зробив окремий випадок перерахунку на кожну помилку, тобто, throw ThisTypeOfError.thisParticularCase).
Арку

6

На основі відповіді @Nick kets, ось більш повний приклад:

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/


1
ТБХ: Я зараз просто роблюthrow NSError(message: "err", code: 0)
еоніст

Отже, ви навіть не використовуєте власний приклад? : D О, і перший аргумент повинен бути domain, чи не messageтак?
NRitH

1
Ваше право, домен. І ні, в код додається занадто багато цукру. Зазвичай я роблю багато невеликих рамок і модулів і намагаюся підтримувати низький рівень цукру для розширення. Сьогодні я намагаюся використовувати суміш між Результатом та NSError
еоністом

6

Якщо вам не потрібно вводити помилку, і ви хочете негайно зупинити програму, ви можете скористатися fatalError: fatalError ("Custom message here")


3
Зауважте, що це не призведе до помилки, яку можна знайти. Це призведе до збою програми.
Аділ Хуссейн

4

Мені подобається відповідь @ Олександра-Борисенка, але локалізований опис не повернуто, коли потрапили як помилка. Здається, що вам потрібно використовувати LocalizedError замість цього:

struct RuntimeError: LocalizedError
{
    let message: String

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

    public var errorDescription: String?
    {
        return message
    }
}

Дивіться цю відповідь для отримання більш детальної інформації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.