LoginSignup
4

More than 1 year has passed since last update.

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

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】アサーションによるプログラムの終了

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

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
What you can do with signing up
4