クロージャ とは
クロージャとは、再利用可能なひとまとまりの処理の事を指します。
ちなみに、関数もクロージャの一種なので共通の機能が多々あります。
関数はfuncキーワードを使い定義していましたが、
クロージャはクロージャ式を使い定義することが出来ます。
クロージャ式とは、名前が不要・型推論による省略が可能など、
関数よりも手軽に定義することができます。
定義方法
クロージャ式の定義は、全体を{ }で囲み、引数・戻り値の型・文を定義します。
引数や戻り値の型は関数と同じですが、
クロージャ式は、戻り値の型と実行文をinキーワードで区切ります。
また、戻り値を返す際は戻り値の型と同型の返り値をreturn文で指定する必要があります。
{(引数名1: 型, 引数名2: 型 ・・・) -> 戻り値の型 in
クロージャの実行時に実行される文
戻り値の型を指定した場合はreturn文で値を返す
}
String型の引数を受け取り、その文字列の文字数を数えたInt型の戻り値を返す、
(String) -> Int型
のクロージャは次のように定義します。
let stringCount = {(string: String) -> Int in
string.count
}
stringCount("Hello World!") // 12
定義方法としては、関数と似たような感じなので理解しやすいと思います。
関数同様に、文が返り値の記述のみで終わっているので、
暗黙的なreturnを使うことができます。
今回の場合の関数と違うところと言えば、
関数はfuncキーワードを使いますが、クロージャは変数や定数に代入しているという点です。
型推論
クロージャの型は、通常の型と同じように扱うことができます。
なので、変数や定数の型や関数の引数の型として利用することもできます。
let closure: (Int) -> Int // (Int) -> Int型
func action(x: (String) -> Int) { ・・・ } // (String) -> Int
変数や定数の宣言時に型の宣言のみを行い、
クロージャ式を代入していない場合は型推論を使えます。
下記では、変数closureの型を(String) -> Int型
と明示的に宣言しているので、
この変数に代入するクロージャの型も同様であると推論できます。
つまり、変数closureに代入するクロージャは、
引数と戻り値の型を省略することができます。
①のパターンは、明示的に引数と戻り値の型を記述したものになります。
返り値には文字数を指定しています。
②のパターンは、引数と戻り値の型を型推論で省略したものになります。
返り値には文字数の2倍の値を指定しています。
var closure: (String) -> Int
// ① - 引数と戻り値の型を明示的に記述した場合
closure = {(string: String) -> Int in
return string.count
}
closure("What is this ?") // 14
// ② - 引数と戻り値の型を省略した場合
closure = {string in
return string.count * 2
}
closure("What is this ?") // 28
変数や定数の宣言時に型を記述していな場合は、
型推論を行うことができないので明示的に引数と戻り値の型を記述するようにしてください。
実行方法
クロージャの呼び出し方は関数と同じで、
クロージャが代入されている変数名や定数名の末尾に( )をつけ、
( )内に引数を , (カンマ)区切りで並べていきます。
変数に代入されているクロージャを実行し、その結果を定数に代入しています。
let 定数名 = 変数名(引数1, 引数2)
引数
クロージャ式の引数は関数の引数の仕様と似ています。
機能の比較としては下記の様になります。
利用可能な機能 | 関数 | クロージャ式 |
---|---|---|
外部引数名 | ○ | × |
デフォルト引数 | ○ | × |
インアウト引数 | ○ | ○ |
可変長引数 | ○ | ○ |
簡略引数名 | × | ○ |
関数を定義する際は、外部引数名と内部引数名をそれぞれ指定できました。
しかし、クロージャ式の場合は外部引数名の指定はできず内部引数名のみ指定できます。
なので、関数で外部引数名を_
に指定している時と同じ状態になります。
let add = {(x: Int, y: Int) -> Int in
return x + y
}
add(1, 2) // 3
デフォルト引数は指定できないので、
デフォルト引数を持ったクロージャを定義しようとするとコンパイルエラーが発生します。
簡略引数名
定義するクロージャが型推論できる場合は、
型や戻り値を省略することができる事は上で記載しました。
型推論をできる様なケースでは、さらに引数名の定義を省略し、
代わりに簡略引数名を利用することができます。
簡略引数名とは、引数のインデックスに$
をつけたものになります。
定数closureの型を、明示的に(Int, Int) -> Bool
型としています。
Int型の引数を二つ渡すと、Bool型の結果が返ってくるということになります。
次に、closure = { }
の部分ですが、
明示的に宣言されているので引数と戻り値の記載は省略できます。
最初の話だと引数と戻り値は省略しても、引数名の記載は必要でした。
「簡略引数名を使わない場合」というのがそれになります。
簡略引数名を使うと、1つ目の引数(0番目)を$0
2つ目の引数(1番目)を$1
と記述します。
let closure: (Int, Int) -> Bool
// 簡略引数名を使わない場合
// closure = { (x, y) in
// x == y
// }
closure = {
$0 == $1
}
closure(10, 10) // true
簡略引数名を無闇に使用すると、
引数が何を意味しているのか分からない可読性の低いコードになります。
なので、非常にシンプルな処理を記述する際は積極的に使用すべきですが、
それ以外の場合は普通に記述した方が可読性の高いコードを書くことができます。
例えば、filter(_:)メソッドなどは単純な処理なので、
簡略引数名を使うとこの様に記述できます。
(int) -> Bool in
の部分は型推論により省略可能なので初略。
int > 20
の、int
は第一引数なので$0
で表すことが可能です。
なので簡略引数名を使用するとここまで短くなります。
let array = [10, 20, 30, 40]
// 簡略引数名を使わない場合
// let filter = array.filter { (int) -> Bool in
// int > 20
// }
let filter = array.filter { $0 > 20 }
filter // [30, 40]
戻り値
クロージャも関数と同様に、
戻り値のあるクロージャと戻り値のないクロージャを定義できます。
クロージャ型の表記においては、引数が存在しない場合は( )を使用し、
戻り値が存在しない場合はVoid型を使用します。
つまり、Void -> Void や ( ) -> ( ) ではなく、
( ) -> Void という形になります。
// 戻り値が存在しないクロージャ
let sampleA: () -> Void = { }
// 戻り値を一つ持つクロージャ
let sampleB: () -> Int = {
return 1
}
以上でクロージャの基礎についての説明について終了したいと思います。
関数と似ていて使い道に少し悩むと思います。
**クロージャの使い方〜応用〜**の記事では、
クロージャだからこそできる機能が載っていますのでご覧ください!
以上、最後までご覧いただきありがとうございました。