04 Swiftコードの書き方: エラー関連処理
エラー関連処理
処理の途中でエラーが発生した際に、そのエラーを通知し(エラー発生情報をオブジェクト間で渡す)、エラーごとに適した処理をプログラマが行えるようにするための仕組みがSwiftには用意されています。関数・呼び出しの書き方に追加していく記法なので、関数の書き方を思い出しながら進めていきましょう。
・エラー発生時の処理の流れ
バケツリレーのように、A→B→Cと関数が呼び出されています。
この図では、最初に呼び出してきた関数Aと、エラーが発生した関数Cに注目してください。
エラーが起こりうる関数(ここではC)に、「今ここでエラーが発生しました!」という情報を投げるためのthrow処理を記載し、その処理は最初の関数(ここではA)に通知されます。このAに、エラー発生時の処理(エラーが起こったら何をするかというコード)を記述しています。間のBにエラー情報は通知されません。
1、エラー通知:エラーが起こりうる関数側で記載するthrow処理
2、エラー対応の処理:戻り値を受け取る側(関数呼び出し側)で記載するdo-catch文
この2つに分けて書き方を見ていきましょう。
throw処理
関数Cのような、エラーの起こりうる可能性がある関数は、次のように記述します。
1 2 3 |
func 関数名(引数名: 型) throws -> 戻り値の型 { throw 呼び出し元へ渡すエラー情報 } |
関数名()の後にthrowsと記載することで、「この関数でエラーが起こる可能性がある」ということを明示します。
エラーが起きた時に、throwとすることで、「エラーが発生した」という情報を呼び出し元に渡します。
throwを使って渡すのは、「エラーが発生したという情報」です。
これは、Errorプロトコル(Apple提供)を適用したenumを作成し、それをthrowの後に呼び出して、呼び出し元に渡します。
Errorプロトコルとは、適用した型はエラーを表現する型として扱う、ということを示すための特別なプロトコルで、実装するプロパティやメソッドはありません。
1 2 3 4 5 6 7 8 9 10 11 12 |
// Errorプロトコルに準拠した独自のエラータイプの宣言 enum MyError: Error { case invalidValue // 用意したError準拠のデータ } // エラーをthrowする可能性がある関数 func doubleUp (value: Int) throws -> Int { if value < 0 { throw MyError.invalidValue } return value * 2 } |
doubleUp関数は渡された引数を2倍にして返します。
この渡された引数valueの値が「0」よりも小さいときに「invalidValue(意:無効な値)」というエラーデータが渡される(throw)記述になっています。
それではこのdoubleUp関数の呼び出し側の処理を見てみましょう。
do-catch文 try文
関数Aのような、エラーが起こりうる関数を呼び出す側は、次のように記述します。
(「//エラーハンドリングが必要な〜」というコメント以下からです。)
(ここでは冒頭の図の関数A・関数Bを省略して、関数C(下記のdoubleUp関数)を、関数の中からではなく直接呼び出しています。Playgroundでそのまま試してみてください。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
// エラータイプの宣言 enum MyError: Error { case invalidValue } // エラーをthrowする可能性がある関数(関数C) func doubleUp (value: Int) throws -> Int { if value < 0 { throw MyError.invalidValue } return value * 2 } // エラーハンドリングが必要なdoubleUp関数を利用----------------------- do { print("正常でもエラーでも最初にここを通ります。") let doubleResultValue = try doubleUp(value: 10) //呼び出し(代入せず右辺だけでもOK。 書きたい処理に合わせて。) print("正常終了!返ってきた値は\(doubleResultValue)") } catch MyError.invalidValue { print("エラー発生") } |
throwsが宣言されている関数を利用する場合は、throwで通知されるエラーをハンドリングする必要があります。
※ハンドリング:状況に合わせて対応処理すること
要は返ってきた情報に合わせてそれぞれに対応する処理を記載します。
1 2 3 4 5 |
do { try 関数の呼び出し処理 } catch 渡されたエラー情報 { エラー情報が渡された時の処理 } |
do { } で囲まれた処理は、通常の値が戻ってきた時の処理、
catch { } で囲まれた処理は、指定したエラー情報が渡された時に行う処理です。
catch文はエラーごと(enum にエラーパターンを複数作る)に複数書けます。(do-catch-catch...と続けて書く事ができる)
サンプルコードで正常処理とエラー処理を実行して動作を確認しましょう。
必ず処理するdefer処理
throws, do-catchの内容を踏まえた上で見ていきましょう。
関数の処理にかかる時間を計測しようとした場合に、関数呼び出しの前後で現在時刻を出力する処理を記述することを考えることがあると思います。次のような例です。現在日時をプロパティとして持つDateクラス(Xcodeで提供されているクラス、import UIKitの記載が必要)のインスタンスをprint文で出力することで、その時点での時刻を出力しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
//エラーをthrowする可能性がある関数 func longMethod() throws { <省略>時間がかかる処理 } //エラーハンドリングが必要なlongMethod関数を利用 do { print("処理開始時間: \(Date())") //日付を取得するDateクラスを前後に挟む try longMethod() //関数呼び出し print("処理終了時間: \(Date())") } catch { print("エラー発生") } |
結果例は次のとおりです。この時、処理開始時間と処理終了時間の差が20秒であることがわかります。
1 2 |
処理開始時間: 2018-11-01 02:10:10 +0000 処理終了時間: 2018-11-01 02:10:30 +0000 |
一見問題なさそうですが、例で使用しているlongMethod関数は、エラー時にthrowを発生させ、catch処理が行われる可能性があります。次の例は、throwが発生した場合の出力結果です。
1 2 |
処理開始時間:2018-11-01 02:10:10 +0000 エラー発生 |
処理終了時間が出力されていないことがわかります。エラー発生時にも処理終了時間を出力したい場合にはどうしたらよいのでしょうか?
その際に活躍するのがdeferです。defer内に定義した処理は、スコープ(=範囲:deferが書かれた場所の{ }内のこと)終了時に呼び出されることが保証されます。deferは次の書式で宣言できます。
1 2 3 |
defer{ スコープ終了時に行いたい処理 } |
deferを使用して、先ほどの処理終了時間がエラー時にも出力されるようにするには、次のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 |
//エラーハンドリングが必要なdoubleUp関数を利用 do { // 処理終了時に必ず行う処理 defer{ print("処理終了時間: \(Date())") } print("処理開始時間: \(Date())") try longMethod() // 関数呼び出し } catch { print("エラー発生") } |
deferを利用しているコードでthrowが発生しなかった場合と、throwが発生したときの実行結果は、それぞれ次のようになります。
1 2 |
処理開始時間: 2018-11-01 02:10:10 +0000 処理終了時間: 2018-11-01 02:10:30 +0000 |
1 2 3 |
処理開始時間:2018-11-01 02:10:10 +0000 処理終了時間:2018-11-01 02:10:15 +0000 エラー発生 |
→次ページ「00 概要(レクチャー3:iOSの部品)」へ