訂正:以下「クロージャ表現」という表記を「クロージャ式」と改めました(2014/7/1)
クロージャ
クロージャは関数と無名で使える関数(クロージャ式)を包括したような概念です。Swiftの公式ガイドには「グローバル関数やネスト関数はクロージャの特別な場合(special cases of closures)である」と書かれています。
クロージャの特徴の1つにキャプチャがあります。キャプチャとは、クロージャ外で宣言された定数、変数をクロージャ内部に取り込む仕組みです。例えば、関数内で宣言された定数や変数は、通常、関数の実行終了時に自動的に破棄されますが、クロージャにキャプチャされた定数や変数は、クロージャ自体が破棄されるまで値を保持し続けます。
クロージャは、次の3つの形式でコード中に現れます。
●グローバル関数
グローバル領域で宣言された一般的な関数です。ただし、定数、変数をキャプチャしません。
●ネストされた関数
関数の中で定義されるネスト関数は、定義されるコンテキストにある定数、変数をキャプチャします。
●クロージャ式
クロージャを関数と比べて簡単な方式で書けるようにしたもので、関数の末尾に引数として多く現れます。呼び出されるコンテキストにある定数、変数をキャプチャします。
Swiftの公式ガイドにおいて、コンテキストという用語は、コードの書かれるクロージャ・インスタンスメソッドからアクセス可能な範囲を示しています。関数内であれば、それが定義されたグローバル変数、定数、ローカル変数などが関数のコンテキストに含まれます。クラス内であれば、そのクラスのコンテキストは、他のクラスのメソッド内のローカル変数を含みません。
関数が参照型のインスタンスとして扱えるのと同様に、クロージャ式も参照型のインスタンスとして扱えます。
なお、クロージャにキャプチャされる定数、変数の種類によっては循環参照が引き起こされやすく、メモリリークの原因になります。そのため、キャプチャを避ける仕組みも用意されています(weak/unownedキーワード)。この仕組みを理解するには、クラス・ストラクチャの概要とオプショナル列挙型の知識が不可欠なので、解説は次回以降に行います。
クロージャ式
クロージャ式は、次の一般的な形で記述できます。
{ (引数宣言) -> 返り値の型 in 文 }
クロージャ式は、Objective-Cにおけるblocksのように、関数の引数として記述することができます。次のコードは、Int -> Bool型の関数を引数として持つフィルタ関数filterNumsを宣言しています。フィルタ関数とは、値の集合と条件を受け取り、値の集合から条件に合致する値だけを集め、それを集合として返す関数のことです。
func filterNums(nums: [Int], cond: Int -> Bool) -> [Int] { var filteredNums: [Int] = [] for num in nums { if cond(num) { filteredNums += num } } return filteredNums } func overTen(a: Int) -> Bool { return a > 10 } filterNums([1,6,8,10,11,4,21], overTen) // [11, 21]
overTenは引数が10より大きな値のときにtrueを返す関数で、フィルタ関数filterNumsに与えられる条件となっています。この条件はoverTen関数に代えて、クロージャ式にすることができます。
filterNums([1,6,8,10,11,4,21], {(a: Int) -> Bool in return a > 10})
関数引数には型推論が行われるため、クロージャ式の引数の型宣言(:Int)と返り値の型宣言(-> Bool)は省くことができます。
filterNums([1,6,8,10,11,4,21], {a in return a > 10})
inの後に続く文が1つである場合、returnキーワードは省略可能です。
filterNums([1,6,8,10,11,4,21], {a in a > 10})
さらに、$0, $1,...で引数にアクセスする機構が備わっています。$0で1つ目の引数、$1で2つ目の引数にアクセスできます。この機構を使用する場合、引数の宣言とinも省けます。
filterNums([1,6,8,10,11,4,21], {$0 > 10})
演算子に対しては、さらに省略できる記法がクロージャ式には備わっていますが、その解説は演算子を取り上げる回で行うことにします。
引数の末尾にあるクロージャ式は、次のように末尾クロージャ式にする(引数のカッコの外に置く)ことも可能です。
filterNums([1,6,8,10,11,4,21]){$0 > 10}