My Favorite Things - Coding or die.

とある技術者の経験記録、的な。

Swift3 例外メモ(try、try!、try?、do-catch)

try?の仕組みとか曖昧になってしまったのでメモ。

例外のスロー

  • 例外をスローする関数はthrowsを宣言(書く位置は引数宣言の直後)
  • スローする例外はErrorプロトコルを実装している必要がある
enum MyError: Error {
    case notFound
    case fail(String)
}

func fatalError() throws -> String {
    throw MyError.fail("fatal error.")
}

呼び出し元

すべての例外の補足

  • do { try throws宣言された関数呼び出し } catch { ... }といった構文で例外をキャッチ
  • throws宣言された関数を呼び出すときには、頭にtrytry!try?のいずれかをつける
  • catch { ... }またはcatch let error { ... }ですべての例外をキャッチ(switchdefaultみたいな)
  • enumのようにエラーの種類から網羅性はチェックされないので、網羅するにはすべての例外を補足するcatchが必須
func catchAllErrors() {
    do {
        try fatalError() // `throws`宣言された関数を呼ぶには`try`が必要
    } catch MyError.notFound { // エラーの種類を記述
        // ...
    } catch MyError.fail(let message) { // enumの関連値はパターンマッチで取得できる
        // ...
    } catch let error { // enumと違って網羅するにはこれが必須
        // ...
    }
}

部分的に例外を補足

  • すべての例外を補足しない場合は、関数の宣言にthrowsを記述する必要がある
func catchSomeErrors() throws { // 例外を網羅できていないので`throws`宣言が必須
    do {
        try fatalError()
    } catch MyError.notFound {
        // ...
    }
}

例外を強制的に無視する(try!)

  • try!を使うと例外を無視できる(do-catchが不要)
  • ただし例外が発生したときにはクラッシュする(強制アンラップと同じ挙動)
func forceCall() {
    try! notError() // 例外を無視する(が、発生したときはクラッシュする)
}

例外を安全的に無視する(try?)

  • try?を使うと、try!と同じように例外を安全に無視できる
  • try!と違い、例外が発生した場合でも単純に無視される
  • 呼び出した関数に戻り値がある場合は、戻り値がnilとなる(失敗した、というニュアンスか)
func safeCall() {
    try? fatalError() // 戻り値がない場合はセーフな呼び出し
    let s1 = try? fatalError() // => nil
    let s2 = try? someString() // => some string
}

まとめ

個人的にこんな感じで考えたらよいのかなという指針。

  • 発生した例外に興味がなければ、try?を使って例外を無視する
  • 発生した例外に興味がある場合は、do { try xxx() } catch { ... }を使って補足
  • 入力値が完全に固定など、確実に例外が発生しないと断言できる場合はtry!を例外的に使う(かも)

個人的にはEitherあるいはResultのほうが好きなのだけれど、シンタックスシュガーがよく出来ているので場合によっては例外の使用を検討しても良いのかも。