Можно ли исключить "RuntimeException" в Swift без объявления?

Я хотел бы сделать исключение из некоторой "глубокой" функции, поэтому он пузырится до другой функции, где я хочу ее поймать.

f1 вызывает f2 вызывает f3 вызовы... fN, которые могут вызывать ошибку

Я хотел бы поймать ошибку из f1.

Я читал, что в Swift мне нужно объявить все методы с помощью throws, а также вызвать их с помощью try.

Но это довольно раздражает:

enum MyErrorType : ErrorType {
    case SomeError
}

func f1() {
    do {
        try f2()
    } catch {
        print("recovered")
    }
}

func f2() throws {
    try f3()
}

func f3() throws {
    try f4()
}

...

func fN() throws {
    if (someCondition) {
      throw MyErrorType.SomeError
    }
}

Не существует ли аналогичная концепция для RuntimeException в Java, где throws не протекает по всей цепочке вызовов?

Ответ 1

Механизм обработки ошибок в Swift не требует привлечения исключенных исключений (runtime). Вместо этого требуется явная обработка ошибок. Swift - это, конечно, не единственный недавно разработанный язык для этого дизайна - например Rust и Go также по-своему также требует явного описания путей ошибок в вашем коде. В Objective-C существует исключенная функция исключения, но в основном используется только для передачи ошибок программиста с заметным исключением нескольких ключевых классов Cocoa, таких как NSFileHandle, которые имеют тенденцию вылавливать людей.

Технически у вас есть возможность поднять Objective-C исключения в Swift с помощью NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise(), как объясняется в этом превосходном ответе, на этот вопрос, возможно дубликат вашего вопроса. Вы действительно не должны поднимать NSExceptions (не в последнюю очередь потому, что у вас нет функции Objective-C), которая доступна вам в Swift).

Почему они пошли с этим дизайном? Apple "Обработка ошибок в Swift 2.0" документ четко объясняет обоснование. Цитата оттуда:

Этот подход [...] очень похож на модель обработки ошибок вручную реализованный в Objective-C с помощью соглашения NSError. Примечательно, что подход сохраняет эти преимущества этой конвенции:

  • Является ли метод причиной ошибки (или нет) является явной частью его контракта API.
  • По умолчанию методы не производят ошибок, если они явно не отмечены.
  • Поток управления внутри функции по-прежнему в основном явный: разработчик может точно определить, какие операторы могут вызывать ошибку, и простая проверка показывает, как функция реагирует на ошибку.
  • Выброс ошибки обеспечивает аналогичную производительность при распределении ошибки и ее возврате - это не дорогостоящий столовый стек процесс разматывания. Cocoa API, использующие стандартные шаблоны NSError, могут быть автоматически импортируется в этот мир. Другие общие шаблоны (например, CFError, errno) могут быть добавлены к модели в будущих версиях Swift.

[...]

Что касается базового синтаксиса, мы решили придерживаться знакомого языка Обработка исключений. [...] в целом, распространение ошибок в этом предложение работает так же, как и при обработке исключений, и люди неизбежно собирается установить связь.

Ответ 2

Да, это возможно!

Используйте: fatalError("your message here"), чтобы вызвать исключение во время выполнения

Ответ 3

Чтобы уточнить ответ Максима Мартынова, в Swift есть 3 способа создания необъявленных, неуловимых ошибок (но возможны и другие подходы, если вы хотите выйти за пределы стандартной библиотеки Swift). Они основаны на трех уровнях оптимизации:

  1. -Onone: нет оптимизации; отладка сборки
  2. -O: нормальная оптимизация; выпуск сборки
  3. -O SWIFT_DISABLE_SAFETY_CHECKS: непроверенная оптимизация; чрезвычайно оптимизированная сборка

1. assertionFailure(_:)

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

У него есть сестринская функция с именем assert(_:_:), которая позволяет вам утверждать во время выполнения, является ли условие истинным. assertionFailure(_:) - это то, что вы пишете, когда знаете, что ситуация всегда плохая, но не думайте, что это сильно повредит производственному коду.

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

if color.red > 0 {
    assertionFailure("The UI should have guaranteed the red level stays at 0")
    color = NSColor(red: 0, green: color.green, blue: color.blue)
}

2. preconditionFailure(_:)

Напишите эту строку, если вы уверены, что какое-то условие, которое вы описали (в документации и т.д.), Не было выполнено. Это работает как assertionFailure(_:), но в сборках релизов так же, как и в отладочных.

Как и в случае с assertionFailure(_:), эта функция получила сестринскую функцию precondition(_:_:), которая позволяет вам во время выполнения решить, было ли выполнено предварительное условие. preconditionFailure(_:), по сути, таков, но при условии, что предварительное условие никогда не будет выполнено, как только программа достигнет этой линии.

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

guard index >= 0 else {
    preconditionFailure("You passed a negative number as an array index")
    return nil
}

Обратите внимание, что в чрезвычайно оптимизированных сборках не определено, что произойдет, если эта строка будет нажата! Так что, если вы не хотите, чтобы ваше приложение парило, если оно когда-нибудь попадет в эту строку, сделайте уверен, что состояние ошибки поддается обработке.

3. fatalError(_:)

Используется как последнее средство. Когда каждая другая попытка спасти день провалилась, вот ваша ядерная бомба. После печати сообщения, которое вы передаете ему (вместе с файлом и номером строки), программа останавливается на месте.

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

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

#if arch(arm) || arch(arm64)
    fatalError("This app cannot run on this processor")
#endif

Дальнейшее чтение: Быстрые утверждения Энди Барга

Ответ 4

  Разве нет подобного понятия с RuntimeException в Java, где throws не просачивается по всей цепочке вызовов?

В Swift действительно есть обработка ошибок, которая не распространяется во время компиляции.

Однако, прежде чем обсуждать их, я должен сказать, что тот, который вы указали, где вы используете ключевые слова/функции do...catch, try, throw и throws для обработки ошибок, безусловно, самый безопасный и наиболее предпочтительный. Это гарантирует, что каждый раз, когда ошибка может быть выброшена или обнаружена, она обрабатывается правильно. Это полностью исключает неожиданные ошибки, делая весь код более безопасным и предсказуемым. Из-за присущей compile- и безопасности во время выполнения вы должны использовать это везде, где можете.

func loadPreferences() throws -> Data {
    return try Data(contentsOf: preferencesResourceUrl, options: [.mappedIfSafe, .uncached])
}


func start() {
    do {
        self.preferences = try loadPreferences()
    }
    catch {
        print("Failed to load preferences", error)
        assertionFailure()
    }
}
guard let fileSizeInBytes = try? FileManager.default.attributesOfItem(atPath: path)[.size] as? Int64 else {
    assertionFailure("Couldn't get file size")
    return false
}

Есть также assertion, precondition и fatalError, которые я подробно описал в своем ответе от октября 2017 года. Компилятор обеспечивает их разумную обработку, например, гарантируя, что операторы return и другой поток управления размещаются и опускаются, когда это необходимо.

exit входит в эту семью, если ваша цель - немедленно остановить программу.


Если вы выходите за пределы Swift в более широкую экосистему, вы также видите Objective-C NSException. По вашему желанию это может быть выполнено Swift без использования каких-либо языковых функций, защищающих от этого. Убедитесь, что вы задокументировали это! Однако это не может быть пойман одним только Swift! Вы можете написать тонкую обертку Objective-C r, которая позволит вам взаимодействовать с ней в мире Swift.

func silentButDeadly() {
    // ... some operations ...

    guard !shouldThrow else {
        NSException.raise(NSExceptionName("Deadly and silent", format: "Could not handle %@", arguments: withVaList([problematicValue], {$0}))
        return
    }

    // ... some operations ...
}


func devilMayCare() {
    // ... some operations ...

    silentButDeadly()

    // ... some operations ...
}


func moreCautious() {
    do {
        try ObjC.catchException {
            devilMayCare()
        }
    }
    catch {
        print("An NSException was thrown:", error)
        assertionFailure()
    }
}

Конечно, если вы пишете Swift в среде Unix, у вас все еще есть доступ к ужасающему миру прерываний Unix. Вы можете использовать Grand Central Dispatch, чтобы бросать и ловить эти. И, как вы пожелаете, компилятор не сможет предотвратить их выброс.

import Dispatch // or Foundation

signal(SIGINT, SIG_IGN) // // Make sure the signal does not terminate the application.

let sigintSource = DispatchSource.makeSignalSource(signal: SIGINT, queue: .main)
sigintSource.setEventHandler {
    print("Got SIGINT")
    // ...
    exit(0)
}
sigintSource.resume()

exit входит в это семейство, если ваша цель состоит в том, чтобы перехватить его и прочитать его код.