web-dev-qa-db-ja.com

Swiftで宣言せずに「RuntimeException」をスローすることは可能ですか?

いくつかの「深い」関数から例外をスローしたいので、それをキャッチしたい別の関数にバブルアップします。

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

JavaのRuntimeExceptionに似た概念はありませんか?throwsはコールチェーンのずっと上にリークしませんか?

18
Ferran Maylinch

Swiftのエラー処理メカニズムでは、チェックされていない(ランタイム)例外を発生させる必要はありません。代わりに、明示的なエラー処理が必要です。Swiftは、最近設計されたものだけではありません。この設計に使用する言語–たとえば Rust および Go も独自の方法で、コード内のエラーパスを明示的に記述する必要があります。Objective-Cでは、チェックされていない例外機能存在しますが、主にプログラマーエラーの伝達にのみ使用されます。ただし、NSFileHandleなどのいくつかの主要なCocoaクラスは例外で、人々を捕まえる傾向があります。

技術的には、説明されているようにNSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()を使用してSwift)でObjective-C例外を発生させることができます この優れた回答では から- この質問 、おそらくあなたの質問の複製ですが、NSExceptionsを発生させるべきではありません(特に、Swiftで利用できるObjective-C例外キャッチ言語機能がないため)。

なぜ彼らはこのデザインを採用したのですか? Appleの "エラー処理Swift 2.0" ドキュメントは理論的根拠を明確に説明しています。そこから引用:

このアプローチ[…]は、NSError規則を使用してObjective-Cで手動で実装されたエラー処理モデルと非常によく似ています。特に、このアプローチでは、この規則の次の利点が維持されます。

  • メソッドがエラーを生成するかどうかは、そのAPIコントラクトの明示的な部分です。
  • メソッドは、明示的にマークされていない限り、デフォルトでエラーを生成しません。
  • 関数内の制御フローは依然としてほとんど明示的です。メンテナーはどのステートメントがエラーを生成する可能性があるかを正確に知ることができ、簡単な検査で関数がエラーにどのように反応するかが明らかになります。
  • エラーをスローすると、エラーを割り当てて返すのと同様のパフォーマンスが得られます。これは、コストのかかるテーブルベースのスタック巻き戻しプロセスではありません。標準のNSErrorパターンを使用するCocoaAPIは、この世界に自動的にインポートできます。 Swiftの将来のバージョンでは、他の一般的なパターン(CFError、errnoなど)をモデルに追加できます。

[…]

基本的な構文については、おなじみの例外処理言語を使用することにしました。 […]概して、この提案のエラー伝播は例外処理の場合と同じように機能し、人々は必然的に接続を確立しようとします。

5
mz2

はい、可能です!

使用:fatalError("your message here")実行時例外をスローします

МаксимМартыновの答え 、Swiftには、宣言されていない、キャッチできないエラーをスローする3つの方法があります(ただし、 他のアプローチも可能です 必要に応じて) Swiftの標準ライブラリの外に出かける)。これらは、3つのレベルの最適化に基づいています。

  1. _-Onone_:最適化なし。 debugビルド
  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
_

さらに読む: Andy BarghによるSwiftAssertions

10
Ben Leggiero

JavaのRuntimeExceptionに似た概念はありませんか?throwsはコールチェーンのずっと上にリークしませんか?

Swiftには確かに、コンパイル時に伝播しないエラー処理があります。

ただし、それらについて説明する前に、言語のdo...catchtrythrow、およびthrowsキーワードを使用していることを指摘する必要があります。エラーを処理するための/ featuresは、はるかに安全で最も好ましいものです。これにより、エラーがスローまたはキャッチされるたびに、エラーが正しく処理されるようになります。これにより、予期しないエラーが完全に排除され、すべてのコードがより安全で予測可能になります。その固有のコンパイル時および実行時の安全性のために、可能な限りこれを使用する必要があります。

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
}

assertions、preconditions、およびfatalErrorsもあります。これらは 2017年10月の回答で詳しく説明しました 。コンパイラーは、returnステートメントやその他の制御フローが適切な場合に配置および省略されるようにするなど、これらを適切に処理します。

exitは、プログラムをすぐに停止することが目標である場合、このファミリに含まれます。


Swiftの外でより広いエコシステムに挑戦すると、Objective-Cの NSException も表示されます。必要に応じて、これを防ぐ言語機能を使用せずにSwiftでスローできます。必ずそれを文書化してください!ただし、これはSwiftだけではキャッチできません!シンを書くことができますObjective-C wrapper 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()
    }
}

もちろん、Unix環境でSwiftを書いている場合でも、 nix割り込み という恐ろしい世界にアクセスできます。 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は、目標が トラップ であり、そのコードを読み取ることである場合、このファミリに含まれます。

0
Ben Leggiero