【Swift】do-catch文でエラー処理を行う（その１）

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

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

do-catch、優秀ですね！

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

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

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

```swift

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

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

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


```swift

struct SomeError: Error {}

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

実行結果
Failure: SomeError()
```

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

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

```swift

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プロトコルに準拠している型だということは冒頭で説明しました。

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

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

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

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

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

```swift

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

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

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

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

```swift

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キーワードはイニシャライザにも使用できます。

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

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

```swift

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キーワードを指定する場合は、
**最低でも１つのエラーを発生させるクロージャを引数にとる必要があります。**

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

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

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

```swift

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）](https://qiita.com/onishiApp/items/bc5165f77cf0ed9aef3d)

また、他のエラー処理についても記事にしているのでぜひご覧ください。
・[【Swift】Optional\<Wrapped>型でエラー処理を行う](https://qiita.com/onishiApp/items/110cc714139cd3dc7507)
・[【Swift】Result\<Success, Failure>型でエラー処理を行う](https://qiita.com/onishiApp/items/76e66d1504fcf42a9136)
・[【Swift】fatalError関数によるプログラムの終了](https://qiita.com/onishiApp/items/e6f8cd28fe2ea38ebb7a)
・[【Swift】アサーションによるプログラムの終了](https://qiita.com/onishiApp/items/66d65f7b56ae178b52b8)

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