Swift 6.0 beta 日本語化計画 : Swift 6.0 beta
エラー処理
エラー処理 は、プログラム内でのエラー状態にに対応し、回復するプロセスです。Swift は、throw、catch、propagate、および実行時での回復可能なエラーを操作するための第一級のサポートを提供しています。
一部の操作は、常に完全な実行、または有用な出力を生成する事をを保証しません。Optional は値が存在しないことを表すために使用されますが、操作が失敗したときに、あなたのコードがそれに従って対応できるように、失敗の原因を理解するのにそれは便利です。
例としては、ディスク上のファイルからのデータの読み込みと処理のタスクを考えてみて下さい。ファイルが指定されたパスに存在しない、ファイルが読み取り権限を持っていない、またはファイルが互換性のある形式でコード化されていない事を含めて、このタスクが失敗する多くの場合があります。これらのさまざまな状況を区別する事は、いくつかのエラーを解決するために、ユーザーにプログラムが解決できないいくつかのエラーを通信できるようにします。
Swift では、エラーは、Error プロトコルに準拠する型の値によって表されます。この空のプロトコルは、型がエラー処理のために使用できることを示しています。
Swift の列挙型は、通信されるエラーの性質に関する追加情報を可能にする、関連する値と関連するエラー条件のグループを、モデル化するのに特に適しています。たとえば、ゲーム内の自動販売機を操作するのにエラー状態を表す方法は以下のとおりです。
エラーを throw すると、予期しない何かが起こり、実行の通常の流れが継続できないことをあなたは示すことができます。エラーを throw するには throw 文を使用して下さい。たとえば、以下のコードでは、5 つの追加のコインが自動販売機で必要とされていることを示すためにエラーを throw します。
エラーが throw されると、コードの周囲のある部分は、例えば、問題を修正する事によって、別のアプローチを試みて、ユーザに失敗を知らせることによって、エラーを処理するための責任を負わなければなりません。
Swift にはエラーを処理するために 4 つの方法があります。do-catch 文を使用してエラーを処理し、その関数を呼び出すコードに関数からエラーを伝播でき、optional の値としてエラーを処理し、またはエラーが発生しないことを主張する (assert) ことができます。各アプローチは、以下のセクションで説明します。
関数がエラーを throw した場合、それはあなたのプログラムの流れを変えるので、あなたがすぐにエラーを throw できる、コード内の場所を識別できることが重要です。あなたのコード内でこれらの場所を識別するには、エラーを throw できる関数、メソッド、またはイニシャライザを呼び出すコードの一部の前に try キーワードまたは try? や try! バリエーションを書きます。これらのキーワードは、以下のセクションで説明します。
関数、メソッド、またはイニシャライザがエラーを throw できることを示すには、その関数の宣言内に、そのパラメータの後に throws キーワードを書いてください。throws でマークされた関数は、throw する関数 と呼ばれます。関数が、戻り値の型を指定する場合は、戻り矢印 (->) の前に throws キーワードを書いて下さい。
throw する関数は、それを呼び出している所から範囲へと、その中に throw されるエラーを伝播します。
以下の例では、VendingMachine クラスには、要求された項目が使用できなければ、在庫切れのため、または現在入れた量を超えるコストの場合は、適切な VendingMachineError を throws する vend(itemNamed:) メソッドがあります。
vend(itemNamed:) メソッドの実装は、早くメソッドを終了し、スナックを購入する要件のいずれかが満たされていない場合は、適切なエラーを throw するように guard 文を使用しています。throw 文はすぐにプログラム制御を転送するので、アイテムはこれらの要件のすべてを満たしている場合にのみ販売されます。
vend(itemNamed:) メソッドは、それが throws する全てのエラーを伝播するので、このメソッドを呼び出す全てのコードは do-catch 文、try? または try! を使用して、エラーを処理するか伝播し続けなければなりません。例えば、以下の例の buyFavoriteSnack(person:vendingMachine:) 関数も、throws する関数であり、 vend(itemNamed:) メソッドが throws した全てのエラーは buyFavoriteSnack(person:vendingMachine:) 関数が呼び出された時点まで伝播します。
この例では、buyFavoriteSnack(person: vendingMachine:) 関数は、与えられた人のお気に入りのスナックを検索し、vend(itemNamed:) メソッドを呼び出すことによって、彼らのためにそれを購入しようとします。 vend(itemNamed:) メソッドはエラーを throw できるので、その前にある try キーワードで呼び出されます。
throw するイニシャライザは throw する関数と同じようにエラーを伝播できます。たとえば、下に挙げたリスト内の PurchasedSnack 構造体のイニシャライザは、初期化プロセスの一部として throw する関数を呼び出し、呼び出し元にそれらを伝播させることによって発生したエラーを処理します。
コードのブロックを実行して、エラーを処理するために、do-catch 文を使用して下さい。エラーが do 句内のコードによって throw された場合は、そのうちの一つがエラーを処理できるかどうかを決定するために catch 句と一致させられます。
ここで do-catch 文の一般的な形式を挙げます。
句が処理できるエラーを示すために、catch の後にパターンを書いて下さい。catch 句が、パターンを持っていない場合、句は任意のエラーに一致し、error という名のローカル定数にエラーを結合します。パターン一致の詳細については、パターン を参照してください。
例えば、以下のコードは VendingMachineError 列挙体の 3 つすべてのケースに対して一致します。
上記の例では、buyFavoriteSnack(person:vendingMacine) 関数は、それがエラーを throw できるので、try 式で呼び出されます。エラーが throw された場合、実行は伝播を続行できるかどうかを決定する、catch 句にすぐに転送します。パターンが全く一致しない場合、エラーは最後の catch 句によってキャッチされ、ローカルの error 定数に結合されます。エラーが全く throw されない場合、do 文の残りの文が実行されます。
catch 節は、do 節内のコードが throw できるすべての可能なエラーを処理する必要はありません。いずれの catch 節もエラーを処理しない場合、エラーは周囲のスコープに伝播します。ただし、伝播されたエラーは周囲のスコープの どれか で処理されなければなりません。throw しない関数では、囲んでいる do-catch 文がエラーを処理しなければなりません。throw する関数では、囲んでいる do-catch 文か呼び出し側のどちらかがエラーを処理しなければなりません。エラーが処理されずに最上位のスコープに伝播した場合は、実行時エラーが発生します。
たとえば、上記の例では、VendingMachineError ではないエラーが代わりに呼び出し側の関数によって catch されるように書くことができます。
nourish(with:) 関数で、vend(itemNamed:) が VendingMachineError 列挙型の case の 1 つであるエラーを throws した場合、nourish(with:) はメッセージを印刷してエラーを処理します。そうでなければ、nourish(with:) はエラーをその呼び出し元のサイトに伝播します。その後、エラーは一般的な catch 節によって catch されます。
いくつかの関連するエラーを catch する別の方法は、catch の後にコンマで区切ってそれらをリストすることです。例えば:
eat(item:) 関数は、catch すべき自動販売機のエラーをリストし、そのエラーテキストはそのリストのアイテムに対応します。リストされている 3 つのエラーのいずれかが throw された場合、この catch 節は、メッセージを出力することによってそれらを処理します。後で追加されるかもしれない自動販売機のエラーを含め、その他のエラーは周囲のスコープに伝播されます。
エラーを Optional の値に変換することによりそれを処理するのに try? を使用して下さい。try? 式の評価中にエラーが throw された場合は、式の値は nil になります。たとえば、以下のコードでは、x と y は同じ値であり、同じ動作をします:
someThrowingFunction( ) がエラーを throws する場合、x と y の値は nil です。そうでなければ、x と y の値は、関数が返した値です。x と y は、someThrowingFunction( ) が返すどんな型の optional でもありうると言う事に注意して下さい。ここで、関数は整数を返し、x と y は optional の整数です。
try? を使うと、同じようにすべてのエラーを処理したいときに、簡潔なエラー処理コードを書くことができます。たとえば、以下のコードは、データを fetch するためにいくつかのアプローチを使用し、またはアプローチのすべてが失敗した場合は nil を返します。
時には、throw する関数やメソッドが、実際には、実行時にエラーを throw しないとわかるかも知れません。これらの場面では、エラーの伝播を無効にするために式の前に try! と書くことができるし、エラーが throw されない実行時のアサーションで呼び出しを包み込めます。エラーが実際に throw された場合は、実行時エラーが発生します。
たとえば、以下のコードは、与えられたパスにイメージリソースをロードするか、イメージがロードできない場合にはエラーを throws する loadImage(atPath:) 関数を使用しています。この場合、イメージはアプリケーションと共に出荷されているため、実行時にエラーは throw されないので、エラーの伝播を無効にすることは適切です。
上記の例はすべて、最も一般的な種類のエラー処理を使用しており、あなたのコードが throw するエラーは、Error プロトコルに準拠する任意の型の値にすることができます。このアプローチは、コードの実行中に発生する可能性のあるすべてのエラーを事前に知ることはできないという現実と一致しています。特に、他の場所で throw されたエラーを伝播する場合はそうです。また、エラーは時間の経過とともに変化する可能性があるという事実も反映しています。ライブラリの新しいバージョン (依存関係が使用するライブラリを含む) は新しいエラーを throw する可能性があり、実際のユーザー構成の複雑さにより、開発中やテスト中には見えなかった障害モードが明らかになることがあります。上記の例のエラー処理コードには、特定の catch 句がないエラーを処理するためのデフォルトの case が常に含まれています。
ほとんどの Swift コードでは、throw するエラーの型は指定されません。ただし、以下の特殊なケースでは、特定の 1 つの型のエラーのみを throw するようにコードを制限できます。
たとえば、評価を要約し、以下のエラー型を使用するコードを考えてみます。
関数がそのエラーとして StatisticsError 値のみを throw するように指定するには、関数を宣言するときに、throws だけではなく throws(StatisticsError) と記述して下さい。この構文は、宣言で throws の後にエラーの型を記述するため、typed throws(型つきの throws) とも呼ばれます。たとえば、以下の関数は、エラーとして StatisticsError 値を throws します。
上記のコードでは、summarize(_:) 関数が 1 から 3 のスケールで表現された評価のリストを要約します。この関数は、入力が有効でない場合、StatisticsError のインスタンスを throws します。上記のコードでエラーを throw する 2 つの場所では、関数のエラーの型がすでに定義されているため、エラーの型が省略されています。このような関数でエラーを throw する場合は、throw StatisticsError.noRatings と書く代わりに、短縮形の throw .noRatings を使用できます。
関数の先頭に特定のエラーの型を記述すると、Swift は他のエラーが throw されないかをチェックします。たとえば、この章の前半の例の VendingMachineError を上記の summarize(_:) 関数で使用しようとすると、そのコードはコンパイル時にエラーを生成します。
通常の throw する関数内から、型付き throws を使用する関数を呼び出すことができます。
上記のコードでは、someThrowingFunction() のエラーの型を指定していないため、any Error を throws します。エラーの型を明示的に throws(any Error) として記述することもできます。以下のコードは上記のコードと同等です。
このコードでは、someThrowingFunction() は、summarize(_:) が throws するエラーを伝播します。summarize(_:) からのエラーは常に StatisticsError 値であり、これは someThrowingFunction() が throw する有効なエラーでもあります。
戻り値の型を ofNever にして決して戻らない関数を記述できるのと同様に、throws(Never) を使って決して throws しない関数を記述できます。
この関数は、throw する Never 型の値を作成できないため、throw できません。
関数のエラーの型を指定するだけでなく、do-catch 文に特定のエラーの型を記述することもできます。例えば:
このコードでは、do throws(StatisticsError) と記述することで、do-catch 文が StatisticsError 値をそのエラーとして throws することを示します。他の do-catch 文と同様に、catch 句は、考えられるすべてのエラーを処理するか、未処理のエラーを周囲のスコープに伝播させて処理することができます。このコードは、列挙値ごとに 1 つの case を持つ switch 文を使用して、すべてのエラーを処理します。パターンを持たない他の catch 句と同様に、この句はすべてのエラーに一致し、そのエラーを error という名前のローカル定数に結合します。do-catch 文は StatisticsError 値を throws するため、error は StatisticsError 型の値です。
上記の catch 句は、起こりうる各エラーをそれぞれ照合して処理するために switch 文を使用しています。エラー処理コードを更新せずに StatisticsError に新しい case を追加しようとすると、switch 文が網羅的でなくなるため、Swift はあなたにエラーを返します。独自のエラーをすべて catch するライブラリの場合、このアプローチを使用して、新しいエラーを処理するのに対応する新しいコードを確実に取得できます。
関数または do ブロックが単一の型のエラーのみを throws する場合、Swift はこのコードが型付き throws を使用していると推測します。この短い構文を使用すると、上記の do-catch の例を以下のように記述できます。
上記の do-catch ブロックでは、それが throws するエラーの型を指定していませんが、Swift は StatisticsError を throws すると推測します。Swift が型付けされた throws を推測しないようにするには、throws(any Error) を明示的に記述します。
コードの実行が現在のコードブロックを離れる直前に一連の文を実行するには、defer 文を使用して下さい。この文を使用すると、実行が現在のコードブロックを どのように 離れるか (エラーが throw されたために離れたのか、return や break などの文のために離れたのか)に関係なく、必要なクリーンアップを実行できます。たとえば、defer 文を使用して、ファイル記述子を確実に閉じ、手動で割り当てたメモリを確実に解放することができます。
現在の範囲が終了するまで defer 文は実行を延期 (defer) します。この文は、defer キーワードで構成され、文は、後で実行されます。延期された文は、エラーを throw するか、break または return 文のような文のうち、文の外に制御を移すことになる全てのコードを含めることはできません。延期されたアクションは、それらがあなたのソースで書かれた順序とは逆に実行されます。すなわち、最初の defer 文のコードは最後に実行され、第二のコードの defer 文は最後から 2 番目に実行され、という順序で実行されます。ソースコード順の最後の defer 文が最初に実行されます。
上記の例では、open(_:) 関数には対応する close(_:) への呼び出しがあることを確認するために defer 文を使用しています。
エラー処理コードが全く含まれなくても defer 文を使用できます。詳細については 遅延したアクション を参照して下さい。