【Swift】クロージャの使い方。名前の無い関数を作る。(Swift 2.1、XCode 7.2)
クロージャとは
クロージャとは、名前を付けずに定義する関数のことを言う。変数や引数に関数の中身を直接代入するような使い方をする。
クロージャは以下のように定義する。関数名が無いのと、inがあるのが通常の関数とは異なる。for-inのinを連想して混乱するが、それとは別ものである。
1 2 3 4 |
{(引数名:引数の型) -> (戻り値の型) in 処理 return 戻り値 } |
具体的なコードを見てみよう。以下のコードは変数にクロージャを代入する例。「変数名(価格、個数)」でクロージャを実行すると、価格×個数の結果が返ってくる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* ** 変数に代入したクロージャを呼び出す。 */ //変数にクロージャを代入する。 let test = {(price:Int, number:Int) -> Int in var a = price * number return a } //クロージャを実行する print(test(100,3)) //実行結果 //300 |
クロージャの書き方を簡略化する
クロージャは変数や引数に関数の中身を直接記述するので、中身が長いとソースが読みづらくなる。そのため、コードを簡略化できるルールが備わっている。
①「処理が1文の場合はreturnを省略できる」
上記コードは処理が1文しか無いのでreturnを省略して以下のコードのように簡略化できる。
1 2 3 4 5 6 7 8 9 10 11 |
//変数にクロージャを代入する例 let test = {(price:Int, number:Int) -> Int in price * number } //クロージャを実行する print(test(100,3)) //実行結果 //300 |
ちなみに以下のコードのように、処理が複数文あるのにreturnを省略するとエラーが発生する。
1 2 3 4 5 6 7 8 9 10 11 12 |
//変数にクロージャを代入する例 let test = {(price:Int, number:Int) -> Int in var a = 0 price * number } //クロージャを実行する print(test(100,3)) //実行結果 //error: missing return in a closure expected to return 'Int' |
②「処理が1文の場合は戻り値の型を省略できる」
上記コードは処理が1文しか無いので戻り値を省略して以下のコードのように簡略化できる。
1 2 3 4 5 6 7 8 9 10 11 |
//変数にクロージャを代入する例 let test = {(price:Int, number:Int) in price * number } //クロージャを実行する print(test(100,3)) //実行結果 //300 |
ちなみに、以下のコードのように処理が複数文あるのに戻り値を省略するとエラーが発生する。
1 2 3 4 5 6 7 8 9 10 11 12 |
//変数にクロージャを代入する例 let test = {(price:Int, number:Int) in var a = 0 return price * number } //クロージャを実行する print(test(100,3)) //実行結果 //unable to infer closure return type in current context |
③「型が分かっている場合は省略できる」
以下のコードのように型を指定してクロージャの変数を宣言すれば、クロージャの中で型を記述しなくても良い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//クロージャの変数を宣言 let test:(Int, Int) -> Int //変数にクロージャを代入する test = {(price, number) in price * number } //クロージャを実行する print(test(100,3)) //実行結果 //300 |
④「引数が1個の場合は引数を囲む括弧を省略できる」
以下のコードのように、クロージャに与える引数が1個の場合は、引数を囲む括弧は省略できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//クロージャの変数を宣言 let test:Int -> Int //変数にクロージャを代入する test = {price in price * 7 } //クロージャを実行する print(test(100)) //実行結果 //700 |
⑤「引数名にこだわりがなければ引数名を省略できる」
引数名にこだわりが無いのであれば、第1、第2引数をそれぞれ$0、$1とし、inを省略して以下のコードのように短縮できる。スッキリ極まりない。
1 2 3 4 5 6 7 8 9 10 11 12 |
//クロージャの変数を宣言 let test:(Int, Int) -> Int //変数にクロージャを代入する test = {$0 * $1} //クロージャを実行する print(test(100,3)) //実行結果 //300 |
クロージャを引数にして関数を呼び出す
次に、クロージャを引数にして関数を呼び出す例を見てみよう。以下のコードは、価格、個数、クロージャを引数に関数を呼び出し、関数の中でクロージャを実行するものである。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
/* ** クロージャを引数に関数を呼び出す。 */ //価格、個数、クロージャを受け取り、合計金額を出力する関数 func outputPrice(price:Int, number:Int , test:(Int,Int) -> Int){ print(test(price,number)) } //関数を呼び出す outputPrice(200, number:7, test:{(price:Int, number:Int) -> Int in price * number}) //実行結果 //1400 |
先ほどの例と同様に、上記コードを簡略化すると以下のようになる。
1 2 3 4 5 6 7 8 9 10 11 |
//価格、個数、クロージャを受け取り、合計金額を出力する関数 func outputPrice(price:Int, number:Int , test:(Int,Int) -> Int){ print(test(price,number)) } //関数を呼び出す outputPrice(200, number:7, test:{ $0 * $1 }) //実行結果 //1400 |
クロージャの使いどころ
クロージャを学んで思うことは「クロージャを使うより、関数を使った方が分かりやすいのでは?」の人が多いのではないだろうか。
確かに、変数や引数に唐突に入りこんできて一見何をしているのか分かりづらいクロージャを使ったロジックを考えるよりは、関数を使ってコーディングしたほうがソースの可読性も上がり、デバッグがしやすいはずだ。
アプリ開発で絶対にここはクロージャを使った作りにした方が良いという利点が見つかれば、今後の記事で紹介することにする。
ただし、細かいところでクロージャを使う状況はある。例えば、コレクションの並び替えや、抽出などで自前の比較条件を指定するような場合である。
以下のコードは配列から3で割り切れる値を抽出するものである。fileterメソッドの引数にクロージャを与えてフィルタリングを行っている。クロージャは、配列の値を3で割った余りが0になればtrue、0にならなければfalseを返す。filterメソッドはクロージャがtrueと判定したものを抽出条件を満たした値と判断する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* ** クロージャの条件式をもとに配列からデータを抽出する。 */ let hairetsu = [100, 200, 300, 400] //クロージャを代入する変数を宣言 var test:(Int) -> Bool //クロージャを定義 test = {(number:Int) -> Bool in number%3 == 0 } //クロージャを引数にメソッド実行 print(hairetsu.filter(test)) //実行結果 //[300] |
上記のコードを簡略化すると以下のコードになる。スッキリした。
1 2 3 4 5 6 7 8 |
let hairetsu = [100, 200, 300, 400] //クロージャを引数にメソッド実行 print(hairetsu.filter({ $0 % 3 == 0 })) //実行結果 //[300] |
以下のコードはsortメソッドを利用して配列のすべてのデータを降順に並び替えた配列を取得する例である。クロージャでは配列の2つの要素の比較条件を記述する。
1 2 3 4 5 6 7 8 9 10 11 12 |
/* ** クロージャの条件式をもとに配列を並び替える。 */ let hairetsu = [200, 300, 100, 400] //クロージャを引数に配列のデータ並び替えた配列を取得する。 print(hairetsu.sort({ $0 > $1 })) //実行結果 //[400, 300, 200, 100] |
以下のコードは、mapメソッドを利用して配列のすべてのデータに7を足した配列を取得する例である。クロージャでは配列の値に7を加算している。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* ** クロージャの条件式をもとに配列のデータを変更する。 */ //配列 let hairetsu = [100, 200, 300, 400] //クロージャを引数に配列のデータを変更する。 print(hairetsu.map({ $0 + 7 })) //実行結果 //[107, 207, 307, 407] |
以下のコードは、reduceメソッドを利用して配列のすべてのデータを合計する例である。reduceメソッドの第1引数には初期値、第2引数にはクロージャを与える。
1 2 3 4 5 6 7 8 9 10 11 12 |
/* ** クロージャの条件式をもとに配列の合計値を取得する。 */ let hairetsu = [100, 200, 300, 400] //初期値とクロージャを引数に配列のデータを合計を取得する print(hairetsu.reduce(0, combine:{$0 + $1})) //実行結果 //1000 |