クロージャ
ここでは Swift のクロージャ (closures) について学びます。
Swift でのクロージャは主に三つの形式があります。ひとつはグローバル関数 (global functions)。 これは名前付きで、値をキャプチャしないクロージャといえます。二つ目はネストされた関数 (nested functions)。 これは名前付きで、関数を包含するブロックにおける値をキャプチャするクロージャです。
これらは普通に関数と呼ばれるもので、特別な形式のクロージャといえます。
三つ目はクロージャ式 (closure expressions) です。
これは名前無し、かつ、そのコンテキストにおける値をキャプチャするものです。 このページでは三つ目のクロージャ式について説明します。
クロージャ式
クロージャ式 は次の形式で記述できます。
{ (引数1: 型1, 引数2 : 型2, ...) -> 戻り値の型 in return 戻り値 }
具体例を取り上げて説明します。
配列の sort 関数は、「配列の二つの要素を受取り、一つめの引数と二つめの引数がソートしたい順序にあるときに True、そうでない場合に False を返す関数」 を引数として受取ります。
func f( i : Int, j : Int ) -> Bool {
return i > j
}
var nums = [ 3, 10, 1, 5, 2 ]
nums.sort( by: f )
print( nums )
実行結果
[10, 5, 3, 2, 1]
ちなみに > 演算子は上の関数 f と同様に定義されます。このため、上のソートに関しては次のようにシンプルに書けます。
var nums = [ 3, 10, 1, 5, 2 ]
nums.sort( by: > )
print( nums )
ここでは一般論としてクロージャの性質をみていきます。
ここで関数名の f というのは全く重要ではありませんので、名前無しのクロージャ式で記述してみましょう。 クロージャ式を使うと次のように書けます。
var nums = [ 3, 10, 1, 5, 2 ]
nums.sort( by: { ( i : Int, j : Int ) -> Bool in
return i > j
} )
print( nums )
一行で終わるときは in の後で改行する必要は無いので、改行をやめると次のようになります。
nums.sort( by: { ( i : Int, j : Int ) -> Bool in return i > j } )
コードはかなり短くなりましたね。
さらに、nums は Int の配列なので、クロージャ内で記述している Int という型はわかりますので省略できます。さらに、return は比較した結果 (True または False) を 返しているのでこれも Bool しかなりえず型がわかります。
このように全て型がわかる場合は、型の指定も省略できます。すると上記の記述は次のようになります。
nums.sort( by: { i , j in return i > j } )
さらに、単一の式で表されるクロージャでは、暗黙的にその値が返るので return も省略できます。したがって、次のようになります。
nums.sort( by: { i , j in i > j } )
Swift ではインラインクロージャでは自動的に引数の省略名が作成されます。最初の引数から $0, $1, $2, ... という調子です。
これを利用すると、上記の i, j という宣言も不要となり、次のようになります。
nums.sort( by: { $0 > $1 } )
始めの書き方と比較すると、圧倒的に記述が短く簡潔になりましたね。
トレーリング・クロージャ
関数のパラメータリストの最後にクロージャ式が引数となっているときは、そのクロージャ式を関数呼び出しの後に続けて書くことができます。
例えば、先の sort の例では、クロージャ式が唯一の (よって、最後の) 引数です。従って、クロージャを外に出して次のように書けます。
nums.sort() { $0 > $1 }
このように関数の後に続けて記述したクロージャを、トレーリング・クロージャ (trailing closures) といいます。
クロージャの本体の記述が長くなる場合には、このように記述したほうが可読性が上がる場合があります。