クロージャ
クロージャは関数と同様にあるタスクを実行するための実行コードです。クロージャーには以下の3つのパターンがあります。
- 関数
- ネストした関数
- クロージャー式
関数およびネストした関数については下記の記事を参照してください。
Swift 関数(Function) - Codable Tech Blog
Swiftにおいて関数はクロージャーの1つのパターンにすぎません。このページではクロージャー式について見ていきます。
クロージャ式
クロージャー式は名前をもたない関数です。プログラミングの世界では名前のない関数のことを「匿名関数」と呼んだりしまう。
クロージャ式の定義
クロージャ式は次のように定義できます。パラメータや戻り値の型の書き方は関数同等です。
{ (パラメータ,..) -> 戻り値の型 in 文 }
クロージャ式は変数や定数に代入して後から利用したり、関数やメソッドにパラメータとして渡すことが可能です。次の例ではInt型のパラメータを2つ持ち、Int型の戻り値を返すクロージャ式の例です。
但し、クロージャ式単独で記述された行は文と見なされないため、コンパイルエラーが発生します。このため、クロージャ式は変数や定数に代入してあげたり、関数やメソッドのパラメータとして渡してあげる必要があります。
クロージャ式の定数・変数への代入
クロージャ式を定数・変数に代入する場合、次のように明示的に型を指定してクロージャ式を代入するか、型推論を利用して代入します。
var 変数名:関数型 = クロージャ式 or var 変数名 = クロージャ式
関数型は「(パラメータの型)->戻り値の型」という形で表現することができます。
次の例ではInt型のパラメータを2つ持ち、Int型の戻り値を返すクロージャ式を変数addClousureに代入しています。
明示的に型を指定する場合、Int型のパラメータを2つ持ち、Int型の戻り値を返す関数型は(Int,Int)->Intと表現できますので、var addClousure1:(Int,Int)->Intと型指定します。
クロージャ式が代入された定数・変数はクロージャ式を実行することができます。クロージャ式を実行するには、関数同様、丸括弧の中にパラメータを記述して呼び出すだけです。次の例ではaddClousure1にパラメータを渡してaddClousure1に代入されているクロージャ式を実行した例です。
addClousure1のクロージャ式は2つのパラメータを足した値を戻り値として返してくるので、コンソールに「6」と出力されます。型さえ一致していれば、クロージャ式の内容を変更することも可能です。次の例ではaddClousure1に(Int,Int)->Int型のクロージャ式を再代入しています。
このクロージャ式が代入された状態でaddClousure1を呼び出すと、この場合、2つのパラメータを掛けた値を戻り値として返してくるので、コンソールに「9」と出力されます。
関数・メソッドのパラメータとクロージャ式
ここまでに示した例では変数・定数にクロージャ式を代入して実行していました。しかし、クロージャ式が役に立つのは関数やメソッドのパラメータとして利用する時です。次の例は(Int,Int)->Int型のパラメータを持つmyFunc関数を定義し、myFunc関数のパラメータとして(Int,Int)->Int型のクロージャ式を渡して呼び出しています。
myFunc1関数は単純に渡されたクロージャ式を呼び出して、戻り値をコンソールに出力するだけの関数です。このmyFunc1関数を呼び出す時にクロージャ式が代入されたaddClousure1変数とaddClousure2変数を渡しています。この結果、同じmyFunc1関数を呼び出していながら、addClousre1を渡した場合は2つのパラメータの和を出力し、addClousure2を渡した場合は2つのパラメータの積を出力しています。クロージャ式を受け取る関数・メソッドを定義した場合、このように、呼び出し元で呼び出し先の関数の処理の一部分を制御することができます。ほとんどの処理が共通しているのだけど、ある一部だけが処理が異なるケースではクロージャ式を利用した関数を定義することで、無駄のないソースを提供することが可能になります。
関数・メソッド呼び出し時の型推論
クロージャ式を関数に渡す場合、関数のパラメータ定義からクロージャ式の型を推論できます。例えば、次のようなクロージャ式をmyFunc1関数のパラメータとして渡す場合を考えます。
この例ではmyFunc1関数を呼び出す中でクロージャ式を定義して、関数のパラメータとして渡しています。クロージャ式を関数に渡す場合、関数のパラメータ定義から推論できるため、2つのパラメータのInt型と戻り値の型のInt型を省略することが可能です。このため、上記は次のような形で記述することできます。
元々は「(a:Int,b:Int) -> Int」という記述でしたが、型推論により「a,b」だけになり、簡潔な記述を実現することができます。
単独式による暗黙的リターン
クロージャ式が単独式で構成される場合、returnキーワードを省略することが可能です。これは単独式のクロージャー式は暗黙的に単独式の値を返すからです。次の例では先ほどのmuFunc1に渡していたクロージャ式をreturnキーワードを省略した形で渡しています。
このように単独式であればreturnキーワードを省略しても問題ありません。
戻り値とクロージャ式
クロージャ式は次のように戻り値として利用することも可能です。
myFunc2関数はパラメータをもたず、戻り値を返さないクロージャ式を戻り値として設定しています。呼び出し側でmyFunc2関数を呼び出し、戻り値を変数fに代入しています。そして、通常の関数同様()を付けてクロージャ式を呼び出しています。
キャプチャ
ここまでの説明で、クロージャ式は関数と同じように{}ブロックの中の処理が実行されることがわかって頂けかと思います。さらに、クロージャ式では、ブロック外の変数を参照して取り込むことが可能です。なお、ブロック外の変数を参照することをキャプチャすると呼びます。次の例はクロージャ式でキャプチャする例です。
myFunc3関数はInt型のパラメータを受け取り、()->Int型の戻り値を返す関数です。この戻り値として記述されているクロージャ式に注目してください。このクロージャ式ではブロックの外側で定義されている2つの変数を取り込んでいます。この例ではmyFunc3関数のパラメータaと変数ansを取り込んでいます。呼び出し元ではこのクロージャ式を格納した変数f3を3回呼び出しています。この状態では変数aには2、ansには1が代入されている状態です。この状態で、クロージャ式を3回呼び出した動作は次のようになります。
1回目:変数aには2、変数ansには「1(ansの値)×2(aの値)」で2が代入されている。このため、戻り値として2が返却され、コンソールに2が出力される。
2回目:変数aには2、変数ansには「2(ansの値)×2(aの値)」で4が代入されている。このため、戻り値として4が返却され、コンソールに4が出力される。
3回目:変数aには2、変数ansには「4(ansの値)×2(aの値)」で8が代入されている。このため、戻り値として8が返却され、コンソールに8が出力される。
このように、クロージャ式はブロック外の変数を取り込む(キャプチャできる)ことができます。クロージャ式が変数をキャプチャできることから非同期イベントを記述する時にクロージャ式がよく利用されたりします。
トレーリングクロージャ(Trailing Closures)
長いクロージャ式を関数のパラメータとして渡したい場合、トレーリングクロージャ(Trailing Closures)が役にたちます。トレーリングクロージャ(Trailing Closures)は関数・メソッド呼び出し命令の後にクロージャ式を記述する方法です。以下はトレーリングクロージャ(Trailing Closures)の例になります。
//関数定義 func someFunction(closure: () -> ()) { // ... } //通常の方法でクロージャ式をパラメータに渡す someFunction({ //... }) //トレーリングクロージャを利用してクロージャ式をパラメータに渡す //メソッド呼び出しの直後にクロージャー式が記述されている someFunction() { // クロージャ式の内容をここに記述 }
通常の方法に比べて、トレーリングクロージャーを利用しているコードのほうが見通しがよくないでしょうか。コードの見やすさに貢献するトレーリングクロージャですが、利用できるのは関数・メソッドの最後のパラメータが関数型の場合に限ります。次の例はトレーリングリングクロージャを利用してクロージャ式を引き渡しています。
トレーリングクロージャを利用すると、ソースコードの見通しがよくなりますので、利用できる場面では積極的に利用していきましょう。