Можно ли исключить "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). Они основаны на трех уровнях оптимизации:
-Onone
: нет оптимизации; отладка сборки
-O
: нормальная оптимизация; выпуск сборки
-O SWIFT_DISABLE_SAFETY_CHECKS
: непроверенная оптимизация; чрезвычайно оптимизированная сборка
Напишите эту строку, когда вы проводите отладочные тесты, и наберите строку, которую, как вы думаете, никогда не удастся найти. Они удаляются в сборках без отладки, поэтому вы должны предполагать, что они никогда не попадут в производственное приложение.
У него есть сестринская функция с именем 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)
}
Напишите эту строку, если вы уверены, что какое-то условие, которое вы описали (в документации и т.д.), Не было выполнено. Это работает как assertionFailure(_:)
, но в сборках релизов так же, как и в отладочных.
Как и в случае с assertionFailure(_:)
, эта функция получила сестринскую функцию precondition(_:_:)
, которая позволяет вам во время выполнения решить, было ли выполнено предварительное условие. preconditionFailure(_:)
, по сути, таков, но при условии, что предварительное условие никогда не будет выполнено, как только программа достигнет этой линии.
Использование:
guard index >= 0 else {
preconditionFailure("You passed a negative number as an array index")
return nil
}
Обратите внимание, что в чрезвычайно оптимизированных сборках не определено, что произойдет, если эта строка будет нажата! Так что, если вы не хотите, чтобы ваше приложение парило, если оно когда-нибудь попадет в эту строку, сделайте уверен, что состояние ошибки поддается обработке.
Используется как последнее средство. Когда каждая другая попытка спасти день провалилась, вот ваша ядерная бомба. После печати сообщения, которое вы передаете ему (вместе с файлом и номером строки), программа останавливается на месте.
Как только программа попадает в эту строку, эта строка всегда запускается, и программа никогда не продолжается. Это верно даже для чрезвычайно оптимизированных сборок.
Использование:
#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
входит в это семейство, если ваша цель состоит в том, чтобы перехватить его и прочитать его код.