【Swift】エラー処理構文の使い方。do-catchブロックを使ってエラーハンドリングを行う。(Swift 2.1、XCode 7.2)

2020年6月16日

エラーハンドリングとは

プログラム実行中に問題が発生して正常終了できなかった場合に、復帰させる処理を施すことをエラーハンドリングという。

例えば、下図のようにインスタンスAからインスタンスBのメソッドを呼び出したときに、インスタンスBのメソッドの処理でデータ不正や入出力の問題によりメソッドを正常終了できない場合、インスタンスAに対して戻り値や正常終了を返すのでは無く、エラーを投げる。

インスタンスAはインスタンスBから投げられたエラーを受け取ってプログラム実行を継続するための処理を行うといったことだ。

エラーハンドリングの例

 

エラー処理構文を使ってエラーハンドリングをする

では実際にエラーハンドリングを行うサンプルプログラムを作ってみよう。

まず、以下のコードのようにエラーの種類を定義したEnumを作成する。Enumには必ずSwift標準のErrorTypeというプロトコルを適用する必要がある。エラーの名前に決まりは無く、例では数値が低すぎるエラー「LowPrice」、数値が高すぎるエラー「OverPrice」を定義した。

 

次にエラーを投げる可能性のあるメソッドを作成する。以下のコードのように引数と戻り値の間にthrowsを記述すると「このメソッドはエラーを投げる可能性がある」ということを意思表示したことになる。

 

以下のコードは、エラーを投げる可能性のあるクラスの例。値上げメソッドが呼ばれたときに、引数が0以下の場合にLowPriceのエラー、1000以上の場合にOverPriceのエラーを投げるメソッドがある。

 

次に、メソッドを呼び出す側のクラスを作成する。以下のコードのようにメソッド呼び出しの先頭にtryを記述し、それをdoブロックで囲む。tryを記述することで「今から呼び出すメソッドはエラーを投げてくる可能性があります。」ということを意思表示したことになる。

doブロックの下のcatchブロックでエラーの種類ごとに必要な処理を記述する。どのエラーの種類にも当てはまらないエラーの場合はcatchのみのブロックの処理が実行される。

 

以下のコードは、商品クラスのインスタンスの値上げメソッドを呼び出すクラスの例。メソッドを呼び出したあとにエラーが返ってきたら、エラーの種類ごとのエラーメッセージを出力する。

 

上記で作成したクラスを実行すると以下のようになる。

 

エラーにエラーコードやメッセージを付加する

エラーの種類を全部名前で定義している場合ではないときは以下のように定義すればエラーの種類にコードや文字列を持たせることができる。

 

以下のコードはエラーの種類にコードを持たせた例。catchブロックの引数でコードを受け取り、処理の中で使うことができる。文字列の場合も同様に引数で受け取れる。

エラーの伝達

tryをつけてメソッドを呼び出したときの呼び出し先でさらにtryをつけてメソッドを呼び出すことができる。

例えば、下図のようにメソッド呼び出しを繋げていった結果、インスタンスDのinputメソッドがエラーを投げた場合、まずインスタンスCに対してエラーが投げられる。

しかし、インスタンスCにはdo-catchブロックが無いので、インスタンスDから投げられたエラーはインスタンスCを素通りして、インスタンスBに渡り、インスタンスBにもdo-catchブロックが無いので素通りする。そして最終的にインスタンスAのcathブロック内の処理が行われることになる。

エラーの伝達

 

上図のイメージを実際にコードにすると以下のようになる。クラスDから投げられたエラーがクラスAまで伝達され、クラスAのcatchブロックで処理されたことが分かる。

 

メソッド呼び出しをdo-catchブロックで囲んだが、エラーを受けたときにcatchブロックに該当するエラーが無かった場合は、エラーは素通りして呼び出し元に伝達される。

例えば、以下のコードはインスタンスBからインスタンスCのinputメソッドを呼び出したときにインスタンスCからNumberFormatErrorが投げられてきたが、catchブロックにはInputErrorに対する処理しか記述されていないので、エラーが呼び出し元のインスタンスAにエラーが伝達して処理される。

 

最後に必ず実行させたい処理がある場合

エラーが発生してもしなくても最後に必ず実行したい処理がある。例えば、ファイルの入出力でエラーが発生したときのファイルをクローズさせる処理などだ。

そんなときは、以下のコードのようにdeferブロックを利用する。deferブロックの中に書かれた処理は外側のブロックを抜けるときの最後に必ず実行される。

例えば、以下の例ではインスタンスBのinputメソッドを呼び出す前にdeferブロックでメソッドを呼び出したあとに実行する処理を記述する。「テスト終了」、「テスト開始」の順番でコードを記述しているが、実行してみると「テスト開始」、「テスト終了」という順で表示され、deferブロックの処理が最後に行われていることが分かる。

注意点は、defer文はエラーが発生する可能性があるメソッドを呼び出す前に記述しなければならない点である。例えば、以下の例のinputメソッドの後にdeferブロックを移動するとその処理は実行されない。

 

また、こんな使われ方がされるかは微妙だが、deferブロックを複数定義した場合は、あとに定義したほうから順番に実行される。