Swiftには、変数や処理を独自のスコープに閉じ込め、複雑なコードを簡潔に記述できるクロージャが存在します。
この記事では、筆者が疑問に感じた
- クロージャって何?
- クロージャってどうやって書くの?
- 関数とクロージャは何が違うんだ?
- クロージャはどんなところで使われるの?
をもとにクロージャに対する個人的な理解をまとめました。
記事を読んで、ぜひ参考にしてみてください!
クロージャに対する第一印象
筆者はSwiftに対する理解を深めるべく、UdemyでSwiftをゼロから学んでいます。
Swift基礎編でクロージャが紹介されていまいたが、こんな印象を受けました。
この書き方は何なんだ😱
関数が変数に割り当てられているのか!?
関数とクロージャは何がどう違うんだ、、、
クロージャとやらは、どういうところで使われるんだ
書き方難しくないか😇
クロージャの意味や使い方の理解が難しいなと思ったので、「個人的に理解した方法を記事にしてしまおう!」という経緯で書きました。
クロージャの意味や使い方
まずはクロージャが何者なのか、見ていきましょう。
クロージャとは
クロージャは、変数や定数を独自のスコープ内に閉じ込めた独立した処理ブロックです。
- 一般的な関数
- ifやswitchのような条件分岐
- forやwhileのようなループ
これらのコードブロックには、共通する性質があります。
それが、独自のスコープを持っているということです。
以下の例のように、関数内で定義した変数は外から参照することができません。
func myAddition(num1: Int, num2: Int) -> Int {
let total = num1 + num2
return total
}
myAddition(num1: 1, num2: 2) // 3
print(total) // Cannot find 'total' in scope
関数myAddition
の内部で定義した変数total
は、外部からは見えていないです。
言い換えると、関数が独自のスコープをもっているということです。
Swiftでは、この関数のように変数や処理をひとまとまりに扱えるクロージャという概念が存在します。
上の関数をクロージャで書いてみると以下のコードになります。
let myAddition = { (num1: Int, num2: Int) -> Int in
let total = num1 + num2
return total
}
myAddition(1, 2) // 3
構造を比較してみると、どちらもよく似ていますよね〜。
実を言うと、関数もクロージャの一種なのです。
え、関数もクロージャの一種なの😲
似たもの同士ということか〜
いやでも、関数とクロージャは何が違うんだ?
クロージャと関数の大きな違い
クロージャは関数と似たもの同士であり、どちらも変数や処理をひとまとまりに扱える独立したブロックであることを紹介しました。
では、クロージャと関数は何が違うのでしょうか?
結論から言えば、複雑なコードを単純化し、すっきりとしたコードにできるかどうかが最大の違いです。
早速比較してみましょう。
2つの値num1
とnum2
の加算処理myAddition
をprocess_myAddition
が行うことを例にしてみました。
func myAddition(num1: Int, num2: Int) -> Int {
return num1 + num2
}
func process_myAddition() {
let total = myAddition(num1: 1, num2: 2)
print(total)
}
process_myAddition() // 実行結果: 3
func processClosure(closure: (Int, Int) -> Int) {
let total = closure(1, 2)
print(total)
}
processClosure { $0 + $1 } // 実行結果: 3
比較してみてどうでしょうか。
同じコードですが、少しすっきりしたんじゃないかなと思います。
コードの良し悪しは別にして、確実にコード量は少なくなり、少ないコード量で処理を実現できています。
これがクロージャの最大のメリットかと思います。
myAddition
がまるまる消えてる😳
なるほどね〜🤔
クロージャを使うと、実現したい処理を簡潔に書くことができる、と。
クロージャの基本的な書き方
Swiftのクロージャはこのように書きます。
{(引数名: 型) -> 返り値の型 in
処理内容
}
先ほどの関数myAddition
をクロージャに落としこむなら以下の書き方になります。
let myAddition = {(num1: Int, num2: Int) -> Int in
return num1 + num2
}
このクロージャmyAddition
は、Int型の引数num1
とInt型の引数num2
を受け取って、それらの合計をInt型で返すクロージャです。
クロージャの基本的な使い方
ここでは簡単に、以下の2つのパターンでクロージャの基本的な使い方を解説します。
- クロージャのみ
- クロージャと関数の組み合わせ
クロージャのみ
この場合、関数を呼び出す場合と同じように実行します。
let myAddition = { (num1: Int, num2: Int) -> Int in
return num1 + num2
}
myAddition(1, 2) // 3
クロージャと関数の組み合わせ
両者を組み合わせて使う場合、関数の引数にクロージャを指定して呼び出す方法が挙げられます。
/// 2つの整数を足し合わせてその結果を返すクロージャを定義
let myAddition = {(num1: Int, num2: Int) -> Int in
return num1 + num2
}
/// クロージャを受け取ってクロージャの実行結果を出力する関数
func processClosure(closure: (Int, Int) -> Int) {
let total = closure(1, 2)
print(total)
}
processClosure(closure: myAddition) // 3
上の例では、関数processClosure
がクロージャmyAddition
を引数として受け取っています。
中で何が行われているか順を追って見ていきましょう。
関数processClosure
はクロージャmyAddition
をclosure
に代入します。
関数processClosure
内のclosure(1, 2)
は、myAddition(1, 2)
と等価になります。
関数processClosure
内から見て、親スコープにmyAddition(1, 2)
が定義されているため、関数内のクロージャが実行されます。
実行結果3
が定数total
に格納されます。
定数total
が出力されます。
この例だと、関数の内部引数のクロージャに対して、処理方法を外から渡しているイメージか👏
Swiftのクロージャはシンプルに書ける
クロージャをよりシンプルに書くために、以下3つを使います。
- 型を省略:クロージャにあらかじめ型指定を行い、クロージャを代入する際に型を省略
- 引数の簡略化
- 暗黙的リターン
型の省略
クロージャにあらかじめ型指定すれば、 変数にクロージャを代入する際に型指定を省略することが可能です。
let myAddition: (Int, Int) -> Int // あらかじめ型指定
myAddition = {(num1, num2) in // 型の省略
return num1 + num2
}
引数の簡略化
クロージャの引数を$[インデックス]の形で簡略化することが可能です。
let myAddition: (Int, Int) -> Int // あらかじめ型指定
myAddition = {
return $0 + $1 // 型の省略 + 引数の簡略化
}
クロージャmyAddition
は、受け取った引数を順番に$0
,$1
と割り当てます。
暗黙的リターン
関数およびクロージャのコードブロックに1つしか式が含まれていない場合、関数およびクロージャはその式を暗黙的に返します。
したがって、return
は必要ありません。
let myAddition: (Int, Int) -> Int // あらかじめ型指定
myAddition = { $0 + $1 } // 型の省略 + 引数の簡略化 + 暗黙的リターン
いろんな技を駆使してここまでスマートにかけるのか👏
クロージャはどういうところで使われているの?
Swiftでは配列を操作するメソッドが存在し、その操作にクロージャがよく出てきます。
配列を操作するメソッドmap
でクロージャを使ってみます。
map
この関数は、与えられた配列の各要素に対してクロージャを適用した結果を配列に格納して返す関数です。
以下の例は、配列numbers
の各要素を2倍したものを配列doubled
に格納しています。
let numbers: [Int] = [1, 2, 3, 4, 5]
let doubled: [Int] = numbers.map({ (number: Int) -> Int in
return number * 2
})
print(doubled) // 実行結果: [2, 4, 6, 8, 10]
まとめ
この記事では、クロージャの意味や使い方について解説しました。
- クロージャは、変数や定数を独自のスコープ内に閉じ込めた独立した処理ブロック
- クロージャには、コードをシンプルに書ける仕様が備わっている
- 関数の引数にクロージャを指定して呼び出すことができる
- mapやfilterなどの配列操作に関する関数でクロージャは使われている