Swift 6.0 beta 日本語化計画 : Swift 6.0 beta
クロージャ
クロージャ はあなたのコードで渡され使用できる機能の自己完結型のブロックです。Swift におけるクロージャは、他のプログラミング言語でのクロージャ、無名関数、ラムダ、そしてブロックに似ています。
クロージャは、それらが定義されている文脈から任意の定数および変数への参照をキャプチャし、保存できます。これは、それらの定数や変数の クロージング オーバー として知られています。Swift は、あなたのキャプチャしたメモリ管理のすべてを処理します。
グローバルで入れ子になった関数は、関数 で紹介したように、実際にはクロージャの特殊なケースです。クロージャは以下の3つの形式のいずれかを取ります:
Swift のクロージャ式は、一般的なシナリオでは、簡潔で整理された構文を推奨する最適化により、クリーンで、明確なスタイルを持っています。これらの最適化は以下のものを含みます:
入れ子になった関数は、入れ子になった関数 で紹介したように、より大きな関数の一部として、コードの自己完結型のブロックを命名し、定義する便利な手段です。しかし、それは完全な宣言と名前なしに関数のような形式の短いバージョンを記述するのに便利な場合があります。それらの1つ以上の引数として関数を取る関数やメソッドで作業する場合に特にそれは当てはまります。
クロージャ式 は、簡単に一つの行でクロージャを書く方法で、短い焦点を当てる構文です。クロージャ式は、明瞭さまたは意向を見失うことなく省略形でクロージャを記述するためのいくつかの構文の最適化を提供します。クロージャ式の例を以下に挙げ、単一の sorted(by:) メソッドの例を繰り返し磨き上げて、これらの最適化を説明し、より簡潔な方法でそれぞれが同じ機能を表現します。
Swift の標準ライブラリは、あなたが提供するソート(並べ替え)・クロージャの出力に基づいて、既知の型の値の配列を並べ替える sorted(by:) と呼ばれるメソッドを提供します。それがソート処理を完了すると、sorted(by:) メソッドは正しい順序でその要素を並べ替え、古いものと同じ型とサイズの新しい配列を返します。元の配列は sorted(by:) メソッドによっては変更されません。
クロージャ式の例を以下に挙げ、逆アルファベット順で String 値の配列を並べ替える sorted(by:) メソッドを使用します。ここで並べ替えるべき最初の配列はこうです:
sorted(by:) メソッドは、配列の内容と同じ型の 2 つの引数を取るクロージャを受け付け、値が一旦並べ替えされると、最初の値が 2 番目の値の前か後かどうかを告げるために Bool 値を返します。並べ替えするクロージャは最初の値が 2 番目の値の 前 になる場合は true を返す必要があり、それ以外の場合は false を返します。
以下の例では、String 値の配列を並べ替え、並べ替えるクロージャは (String, String) -> Bool 型の関数である必要があります。
並べ替えるクロージャを提供する1つの方法は、正しい型の正常な関数を書くことであり、sorted(by:) メソッドに引数としてそれを渡すことです:
最初の文字列(s1)が、第2の文字列(s2)よりも大きい場合、backward(_:_:) 関数は true を返し、s1 が並べ替えされた配列の S2 の前に表示されるべきことを示します。文字列内の文字が、「より大きい」のは 「よりもアルファベットで後に現れる」事を意味します。これは、文字 "B" は文字 "A" 「より大きく」、文字列 "Tom" は文字列 "Tim" よりも大きいことを意味します。これは、逆順のアルファベットの並べ替えを与え、"Barry" は "Alex" の前に置かれる、などします。
しかし、これは本質的に一つの式の関数 (a > b)が何であるかを書くむしろ長ったらしい方法です。この例では、クロージャ式の構文を使用して、並べ替えるクロージャを一行で記述するほうが好ましいでしょう。
クロージャ式の構文は、以下の一般的な形式です:
クロージャ式構文の パラメータ は、in-out パラメータにする事ができますが、デフォルト値を持つことはできません。可変パラメータに名前を付ければ可変パラメータを使用できます。タプルも、パラメータ型及び戻り値型として使用できます。
以下の例では、以前の backward(_:_:) 関数のクロージャ式のバージョンを示しています。
この一行に書いたクロージャのパラメータと戻り値の型の宣言が backward(_:_:) 関数の宣言と全く同じであることに注意してください。両方の場合とも、それは (s1: String, s2: String) -> Bool として書かれています。しかし、一行に書いたクロージャ式では、パラメータと戻り値の型は中括弧の外ではなく中括弧の 中 に書かれています。
クロージャの本体の先頭は in キーワードによって始まります。このキーワードは、クロージャのパラメータと戻り値の型の定義が終了し、クロージャの本体が、始まろうとしていることを示しています。
クロージャの本体がとても短いので、それは一つの行にさえ書くことができます:
これは sorted(by:) メソッドへの全体的な呼び出しが同じままであることを示しています。括弧のペアは、まだメソッドの引数全体を包んでいます。しかし、その引数は今一行で書いたクロージャです。
並べ替えるクロージャはメソッドに引数として渡されているので、Swift は、そのパラメータの型と、それが返す値の型を推論できます。sorted(by:) メソッドは、文字列の配列上で呼び出されているので、その引数は (String, String) -> Bool 型の関数でなければなりません。これは (String,String) と Bool 型はクロージャ式の定義の一部として書く必要がないことを意味します。型のすべては推測できるので、戻り矢印(->)とパラメータの名前を囲む括弧も省略できます:
一行のクロージャ式として関数やメソッドにクロージャを渡す時、パラメータの型と戻り値の型を推測することが常に可能です。その結果、クロージャが関数やメソッドの引数として使用される場合、完全な形で一行のクロージャを記述する必要は全くありません。
それにもかかわらず、お望みであれば、型を明示的にもできるし、そしてあなたのコードの読者のために曖昧さを回避する場合にはそのように奨励されています。sorted(by:) メソッドの場合、クロージャの目的は、並べ替えが行われている事実から明らかであり、クロージャが文字列の配列の並べ替えを補助しているので、String 値を操作するだろう事を読者は仮定することが安全です。
単一式のクロージャは、前の例のバージョンのように、それらの宣言から return キーワードを省略して、その単一式の結果を暗黙的に返すことができます。
ここでは、sorted(by:) メソッドの引数の関数型は、Bool 値がクロージャによって返されなければならないことを明確にしています。クロージャの本体は、Bool 値を返す単一の式(s1 > s2)を含んでいるので、曖昧さはなく、return キーワードを省略できます。
Swift は、一行で書いたクロージャに省略した引数名を自動的に提供し、$0、$1、$2、 などの名前でクロージャの引数の値を参照するために使用できます。
クロージャ式の中でこれら引数名の省略を使用する時は、その定義からクロージャの引数リストを省略できます。省略した引数名の型は、予想される関数型から推測され、使用する最大番号の省略形引数によって、クロージャが取る引数の数が決まります。クロージャ式はその本体全体で構成されているため、in キーワードも省略できます。
ここで、$0 と $1 はクロージャの第一及び第二の String 引数を参照しています。$1 は最大数の省略形の引数であるため、クロージャは 2 つの引数を取ると理解されます。ここでの sorted(by:) 関数は、引数が両方とも文字列であるクロージャを想定しているため、省略した引数 $0 と $1 は両方とも String 型です。
上記のクロージャ式を書く、さらに 短い 方法が実際にあります。Swift の String 型は String 型の二つのパラメータを持つメソッドとして大なり演算子(>) の文字列固有の実装を定義し、Bool 型の値を返します。これは sorted(by:) メソッドに必要なメソッド型と正確に一致します。したがって、単に、大なり演算子に渡すことができ、そして Swift は、あなたがその文字列固有の実装を使用したいことを推測します。
演算子メソッドの詳細については、演算子メソッド を参照してください。
クロージャ式を関数に、関数の最後の引数として渡す必要がある時、クロージャ式が長い場合は、代わりにそれを 後続クロージャ として書くのが役立ちます。後続クロージャは、それが関数への引数だったとしても、関数呼び出しの括弧の後に書かれます。後続クロージャ構文を使用する時、関数呼び出しの一部として最初のクロージャの引数ラベルを書いてはいけません。
上記の クロージャ式の構文 セクションから文字列並べ替えクロージャを引用すると、後続クロージャとして sorted(by:) メソッドの括弧の外に書くことができます:
クロージャ式が関数またはメソッドの唯一の引数として提供され、後続クロージャとしての式を提供する場合、関数を呼び出すときに、関数やメソッドの名前の後の括弧 ( ) のペアを記述する必要はありません。
1行にインラインで書くことが不可能であるほどクロージャが十分に長い場合、後続クロージャは最も有用です。例として、Swift の Array 型には、その一つだけの引数としてクロージャ式を取る map(_:) メソッドがあります。クロージャは配列内の各項目に対して一度ずつ呼び出され、その項目に(おそらく他の型の)代替マッピングされた値を返します。map(_:) に渡すクロージャにコードを記述することにより、マッピングの性質と戻り値の型を指定します。
提供されたクロージャを各配列要素に任せた後、map(_:) メソッドは、元の配列内の対応する値と同じ順序で新しくマッピングされたすべての値を含む新しい配列を返します。
ここで、Int 値の配列を String 値の配列に変換するために、後続クロージャで map(_:) メソッドを使用できる方法を示します。配列 [16、58、510] は、新しい配列 ["OneSix","FiveEight","FiveOneZero"] を作成するために使われます:
上記のコードは、整数の数字とその名前の英語バージョン間のマッピングの辞書を作成します。また、整数の配列も定義し、文字列に変換される準備ができます。
これで、後続クロージャとして配列の map(_:) メソッドにクロージャ式を渡して、String 値の配列を作成するために numbers の配列を使用できます:
map(_:) メソッドは、配列内の各項目ごとに一度ずつクロージャ式を呼び出します。マッピングされる配列内の値から型が推測できるので、クロージャの入力パラメータ、number の型を指定する必要はありません。
この例では、値は、クロージャ本体内で変更できるように、変数 number は、クロージャの number パラメータの値で初期化されます。(関数とクロージャへのパラメータは、常に定数です。)クロージャ式はまた、マッピングされた出力配列内に保存される型を示すために、String の戻り値の型も指定します。
クロージャ式は、それが呼び出されるたびに output と呼ばれる文字列をビルドします。これは、剰余演算子 (number % 10) を使用して number の最後の桁を計算し、digitNames 辞書内の適切な文字列を検索するためにこの数字を使います。クロージャは、ゼロより大きい任意の整数の文字列表現を作成するために使われます。
digitNames 辞書から得られた文字列は output の 前に 追加され、効果的に数字の逆順で文字列バージョンをビルドします。(式 number % 10 の値は、16 では 6、58 では 8、510 では 0 を与えます)
number 変数はその後 10 で割られます。それが整数であるため、割り算で切り捨てられ、そのため 16 は 1 になり、58 は 5 になり、510 は 51 になります。
プロセスは number が 0 に等しくなるまで続き、その時点で output 文字列がクロージャによって返され、map(_:) メソッドにより出力配列に追加されます。
上記の例の末尾のクロージャ構文を使用すると、クロージャ全体を map(_:) メソッドの外側の括弧内に包み込む必要がなく、クロージャがサポートする関数の直後にクロージャの機能がきちんとカプセル化されています。
関数が複数のクロージャを取る場合は、最初の後続クロージャの引数ラベルを省略し、残りの後続クロージャにラベルを付けて下さい。たとえば、以下の関数はフォトギャラリーの写真をロード(load)します。
この関数を呼び出して写真をロードする場合は、2 つのクロージャを提供して下さい。最初のクロージャは、ダウンロードが成功した後に写真を表示する完了ハンドラーです。2 番目のクロージャは、ユーザにエラーを表示するエラーハンドラーです。
この例では、loadPicture(from:completion:onFailure:) 関数がネットワーク操作をバックグラウンドに送り、ネットワーク操作が終了すると 2 つの完了ハンドラーのいずれか 1 つを呼び出します。このように関数を記述すると、両方の状況を処理する 1 つだけのクロージャを使用する代わりに、ネットワーク障害の処理を担当するコードを、ダウンロードが成功した後にユーザインターフェイスを更新するコードから明確に分離できます。
クロージャは、それが定義されている周囲の文脈から定数と変数を キャプチャ できます。クロージャは、その定数および変数を定義したオリジナルの範囲がもはや存在しない場合でも、その本体内からそれらの定数と変数の値を参照し、変更できます。
Swift では、値をキャプチャできるクロージャの最も簡単な形式は、別の関数の本体内で記述された、入れ子になった関数です。入れ子になった関数は、その外側の関数の引数のどれでもキャプチャでき、また、外側の関数内で定義された全ての定数と変数もキャプチャできます。
ここで入れ子になった incrementer と言う関数を含む makeIncrementer と言う関数の例を挙げましょう。入れ子になった incrementer( ) 関数は、その周囲の文脈から、二つの値、runningTotal と amount をキャプチャします。これらの値をキャプチャした後、incrementer は amount によって、それが呼び出されるたびに runningTotal を増分するクロージャとして makeIncrementer によって返されます。
makeIncrementer の戻り値の型は ( ) -> Int です。これは、単純な値ではなく、関数 を返すことを意味します。それが返す関数にはパラメータはなく、呼び出されるたびに Int 値を返します。関数が、他の関数を返す方法については、戻り値の型としての関数型 を参照してください。
makeIncrementer(forIncrement:) 関数は、返されるインクリメンタの現在実行中の合計を保存するために、runningTotal と呼ばれる整数の変数を定義しています。この変数は、0 の値で初期化されます。
makeIncrementer(forIncrement:) 関数は forIncrement の引数ラベルと、amount のパラメータ名を持つ一つの Int パラメータを持っています。このパラメータに渡された引数の値は、返されたインクリメンタの関数が呼び出されるたびに runningTotal が増分されるべき値を指定します。makeIncrementer 関数は実際の増分を行う、incrementer と言う入れ子になった関数を定義しています。この関数は、単に amount を runningTotal に加算し、その結果を返します。
分離して考えると、入れ子になった incrementer( ) 関数は珍しく見えるかもしれません:
incrementer( ) 関数は、パラメータを全く持っていないし、まだそれはその関数本体内から runningTotal と amount を参照しています。それは、その周囲の関数から runningTotal と amount への 参照 をキャプチャする事によってこれを行い、自身の関数本体内でそれらを使用します。参照によりキャプチャする事は runningTotal と amount が、makeIncrementer への呼び出しが終了しても消えない事を保証し、また runninTotal は incrementer 関数が呼び出される次の回にも使用可能であることも保証します。
ここで、アクション内の makeIncrementer の例を示します。
この例では、その runningTotal 変数に、呼び出されるたびに 10 を追加するインクリメンタ関数を参照する incrementByTen という定数を設定します。関数を複数回呼び出すとこのアクションでの動作がわかるでしょう:
インクリメンタを二回目に作成すると、それは新しい、独立した runningTotal 変数への独自の保存された参照を持つことになるでしょう。
オリジナルのインクリメンタ(incrementByTen)を呼ぶと、再び自身の runningTotal 変数を増分し続け、 incrementBySeven によりキャプチャされた変数には影響しません:
上記の例では、incrementBySeven と incrementByTen は定数ですが、これらの定数が参照するクロージャはまだそれらがキャプチャした runningTotal 変数を増分することができます。これは、関数とクロージャが 参照型 のためです。
関数やクロージャを定数または変数に割り当てる時はいつでも、定数または変数が関数またはクロージャへの 参照 であることを実際に設定しています。上記の例では、それは incrementByTen がそれが定数である事に 参照 しているのはクロージャの選択であり、クロージャ自体の内容ではありません。
2つの異なる定数や変数にクロージャを割り当てた場合、それらの定数または変数の両方とも同じクロージャを参照することもこれは意味します。
上記の例は、alsoIncrementByTen の呼び出しが incrementByTen の呼び出しと同じであることを示しています。両方とも同じクロージャを参照するため、両方とも増分して同じ実行中の合計を増分して返します。
クロージャが関数に引数として渡され、関数が返された後に呼び出される場合、クロージャは関数を エスケープ すると言われます。クロージャをパラメータの 1 つとして受け取る関数を宣言する場合、パラメータの型の前に @escaping を記述して、クロージャがエスケープ可能であることを示すことができます。
クロージャがエスケープできる一つの方法は、関数の外で定義された変数に格納される事です。例として、非同期操作を開始する、多くの関数は完了ハンドラとしてクロージャ引数を取ります。操作を開始した後関数は戻りますが、操作が完了するまでクロージャは呼び出されず、クロージャは後で呼ばれるように、エスケープする必要があります。例えば:
someFunctionWithEscapingClosure(_:) 関数は、その引数としてクロージャをとり、関数の外で宣言されている配列にそれを追加します。@escaping で、この関数のパラメータをマークしなければ、コンパイル時エラーになるでしょう。
self がクラスのインスタンスを参照する場合、self を参照するエスケープクロージャには特別な考慮が必要です。エスケープクロージャで self をキャプチャすると、誤って強力な循環参照を作成しやすくなります。循環参照の詳細については、自動参照カウント を参照してください。
通常、クロージャは、クロージャの本体内で変数を使用して暗黙的に変数をキャプチャしますが、この場合は明示的にする必要があります。self をキャプチャしたい場合は、使用するときに明示的に self を記述するか、クロージャのキャプチャリストに self を含めます。self を明示的に記述することで、意図を表現でき、循環参照がないことを確認するように促されます。たとえば、以下のコードでは、 someFunctionWithEscapingClosure(_:) に渡されるクロージャは self を明示的に参照しています。対照的に、 someFunctionWithNonescapingClosure(_:) に渡されるクロージャは、エスケープなしのクロージャです。つまり、暗黙的に self を参照できるという意味です。
以下に、クロージャのキャプチャリストに含めることで self をキャプチャし、暗黙的に self を参照する doSomething( ) のバージョンを示します。
self が構造体または列挙型のインスタンスの場合、いつでも暗黙的に self を参照できます。ただし、self が構造体または列挙型のインスタンスの場合、エスケープクロージャは self への変更可能な参照をキャプチャできません。構造体と列挙型は値型 で説明しているように、構造体と列挙型では共有の可変性は許可されていません。
上記の例の someFunctionWithEscapingClosure 関数への呼び出しは、mutating メソッド内にあるためエラーになり、self は変更可能です。これは、クロージャをエスケープしても構造体の self への変更可能な参照をキャプチャできないという規則に違反しています。
オートクロージャ は関数への引数として渡される式を包み込むために自動的に作成されるクロージャです。これは、引数を全く取りませんし、呼び出された時、その内側に包み込まれていた式の値を返します。この構文上の利便性は、明示的なクロージャの代わりに、普通の式を記述することにより、関数のパラメータの周りのカッコを省略できます。
オートクロージャを取る関数を 呼び出す のは一般的ですが、この種の関数を 実装する のは一般的ではありません。例えば、assert(condition:message:file:line:) 関数は、その condition と messeage パラメータ用のオートクロージャを取りますが、その condition パラメータはデバッグビルドでのみ評価され、その message パラメータは、condition が false の場合にのみ評価されます。
オートクロージャは、あなたがクロージャを呼び出すまで、内部のコードは実行されませんので、評価を遅らせることができます。そのコードが評価されるときは、あなたがコントロールできますので、評価を遅らせることは、そのコードが副作用を持っているか、計算コストが高い場合に有用です。以下のコードは、クロージャが評価を遅らせる方法を示しています。
customersInLine 配列の最初の要素がクロージャ内のコードによって除去されているにもかかわらず、クロージャが実際に呼び出されるまで、配列の要素は除去されません。クロージャが決して呼び出されない場合は、クロージャ内の式は評価されることは決してなく、それは配列の要素が決して除去されないことを意味します。customerProvider の型は String ではなく、文字列を返す、パラメータのない ( ) -> String の関数であることに注意してください。
関数に引数としてクロージャを渡すときは、遅延評価と同じ動作を得られます。
上記のリストの serve(customer:) 関数は、顧客(customer) の名前を返す、明示的なクロージャをとります。下記の serve(customer:) のバージョンは、同じ操作を実行しますが、明示的なクロージャを取る代わりに、それは @autoclosure 属性でそのパラメータの型をマークすることによってオートクロージャを取ります。今や、それが クロージャではなく、String 引数を取ったかのように、関数を呼び出すことができます。customerProvider のパラメータの型が @autoclosure 属性でマークされているため、引数は自動的に、クロージャに変換されます。
エスケープするのを許可されているオートクロージャが欲しい場合は、@autoclosure と @escaping 属性の両方を使用します。@escaping 属性は上記の クロージャのエスケープ で説明しています。
上記のコードでは、その customerProvider 引数として渡されたクロージャを呼び出す代わりに、 collectCustomerProviders(_:) 関数は customerProviders 配列にクロージャを付け加えます。配列は、関数の範囲外で宣言されており、それは配列の中のクロージャが関数から戻った後に実行できることを意味します。その結果、customerProvider 引数の値は、関数の範囲をエスケープすることを許されていなければなりません。