5
4

More than 3 years have passed since last update.

【Swift】do-catch文でエラー処理を行う(その1)

Last updated at Posted at 2020-12-24

do-catch文

do-catch文を使ったエラー処理では、
エラーが発生する可能性がある処理をdo節内に記述し、
エラーが発生するとcatch節に移動します。

なお、catch節内ではエラーの詳細情報にアクセスできるので、
Result<Success, Failure>型と同様にエラー詳細を用いた処理を行うことができます。

do-catch、優秀ですね!

実装方法

do-catch文では、throw文によるエラーが発生し得る処理をdo節内に記述し、
catch節にエラー処理を記述します。

throw文はエラーを発生させる文で、
Errorプロトコルに準拠した値を使います。

また、catch節では暗黙的に宣言された定数errorを使うことができます。


do {
   throw文によるエラーが発生する可能性のある処理
} catch {
   エラー処理
   定数errorを通じてエラー値にアクセス可能
}

次のサンプルコードでは、
Errorプロトコルに準拠したSomeError型を定義し、エラーを発生させています。

print( )が実行される前にcatch節へ移行するため、
do節のprint( )でなく、catch節のprint( )が実行されます。


struct SomeError: Error {}

do {
    throw SomeError()
    print("Success")
} catch {
    print("Failure: \(SomeError())")
}

実行結果
Failure: SomeError()

catch節ではエラーを分けることもできます。
分ける方法としては、パターンマッチを使います。

パターンマッチの文法はSwitch文と同様で、
全てのケースを網羅しなければならなかったり、
defaultキーワードの役割のcatch節も存在します。


enum SomeError: Error {
    case error
    case warning(String)
}

do {
    throw SomeError.warning("なんか変だよ?")
} catch SomeError.error {
    print("Error")
} catch SomeError.warning(let reason) {
    print("Warning: \(reason)")
} catch {
    print(error)
}

実行結果
Warning: なんか変だよ

Errorプロトコル

throw文のエラーを表現する型は、
Errorプロトコルに準拠している型だということは冒頭で説明しました。

型は基本的に列挙型で定義します。
それにも理由があり、発生するエラーを網羅的に記述できるからです。

また、プログラム全体で起こり得るあらゆるエラーを1つの型に記述するのではなく、
エラーの種類ごとに型を定義することが一般的らしいです。

この理由としては、パターンマッチを記述する時に、
全てのケースを記述する必要があるからだと勝手に思っています。

throwsキーワード

throwsキーワードは関数やイニシャライザ、クロージャの定義に追加します。

そうすることにより、
do-catch文を用いずにthrow文によるエラー処理を発生させることができます。


func 関数名(引数) throws -> 戻り値の型 {
    throw文によるエラーが発生する可能性のある処理
}

次のサンプルコードでは、
引数に貰った値を2倍にして返す関数を定義しています。

2倍した際にInt型の許容範囲を超えてしまう可能性があるので、
guard文でint <= Int.max / 2のように確認しています。

Int.max / 2の値よりも引数の値が大きかった場合は
エラー処理を行う流れになっております。


enum OperationError: Error {
    case overCapacity
}

func double(value int: Int) throws -> Int {
    guard int <= Int.max / 2  else {
        throw OperationError.overCapacity
    }

    return int * 2
}

throwsキーワードはイニシャライザにも使用できます。

イニシャライザで使う場合は、
インスタンス化の途中で発生したエラーを呼び出し元に伝えることができます。

次のサンプルコードでは、
引数で貰った値が奇数だった場合はエラーを起こすようにしています。


enum CheckValue: Error {
    case valueIsOdd
}

struct Sample {
    let value: Int

    init(value: Int) throws {
        guard value % 2 == 0 else {
            throw CheckValue.valueIsOdd
        }
        self.value = value
    }
}

rethrowsキーワード

この記事を書くにあたり初めて聞いたキーワードなのですが、
rethrowsキーワードは、関数やメソッドをrethrowsキーワードを指定して定義することで、
引数のクロージャが発生させるエラーを関数の呼び出し元に伝播させる事ができるらしいです。

つまり、rethrowsキーワードを指定する場合は、
最低でも1つのエラーを発生させるクロージャを引数にとる必要があります。

つまり、下記のサンプルコードの状態です。
実際に書いてみたけど難しい!!(笑)

関数のdivision( )は第一引数にInt型の値を、第二引数にクロージャをもらいます。
そして偶数だった場合は2で割った値を戻り値に、奇数だった場合はエラー処理を行います。
(tryについてはこの記事の続きで説明します。)

割り切れる値だった場合は、
throw SomeError.valueIsOddが実行されずにvalueの値が2で破られた値になります。


enum SomeError: Error {
    case valueIsOdd
}

func division(value: Int, closure: (_ value: Int) throws -> Void) rethrows -> Int {
    // 引数で貰ったvalueを2で割って余りが0ではなかったらguard内を実行
    guard value % 2 == 0 else {
        // クロージャを実行
        try closure(value)
        return value
    }
    return value / 2
}

var value = 0

do {
    // エラーを起こす可能性があるのでdo節内に記述する
    try value = division(value: 11, closure: { (value) in
        // 割り切れなかったらクロージャが実行される
        throw SomeError.valueIsOdd
    })
} catch {
    print(error)
}

print(value)

実行結果
valueIsOdd
0

まだ実装方法の途中ですがキリがいいのでここまでにします。

続きはtryキーワードについて説明していますのでぜひご覧ください。
-> 【Swift】do-catch文でエラー処理を行う(その2)

また、他のエラー処理についても記事にしているのでぜひご覧ください。
【Swift】Optional<Wrapped>型でエラー処理を行う
【Swift】Result<Success, Failure>型でエラー処理を行う
【Swift】fatalError関数によるプログラムの終了
【Swift】アサーションによるプログラムの終了

最後までご覧いただきありがとうございました。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4