「 よく聞く『クロージャ』って何のこと…? 」
「 関数とは一体何が違うの…? 」
Swift言語における機能の一つ「クロージャ」は、多くのプログラムで活用されており、無くてはならない重要な機能です。
そして、「このコードって何でこうなってるの…?」が発生する要因となりやすい機能でもあります。
ここでは、「クロージャとは何なのか」を始めに、「クロージャが持つ機能」や「使い方」について見ていきます。
本記事を活用して、クロージャに慣れていきましょう!
[ 本記事はこんな人におすすめ ]
・Swift言語でプログラミングの基礎を学習している
・クロージャとは何なのかを知りたい
・クロージャの使い方を知りたい
クロージャ(Closure)とは、Swift言語における機能の一つで、「{ }」で囲まれた何らかの処理を持つブロック(コードの塊)のことを指します。
例えば以下のような、SwiftUIのビュー機能Button
も、クロージャによってまとまったコードが実行されています。
上記のクロージャを説明するなら、
『このボタンのクロージャは、”ボタンをタップしました”というメッセージをプリント出力する処理を持っている』
と、言うことができます。
クロージャの主な機能と特徴を、先に挙げておきます。
本記事ではこれらの項目に焦点を置いて、クロージャを解説していきます。
まとまったコード処理を実行できる
定数や変数に代入することが可能
名前を持たない(無名関数)
コードを簡潔にできる(省略形)
はじめのうちは、クロージャについてのイメージがあまりピンと来ないかもしれません(筆者はかなり苦戦しました…)。
このあと少しずつ紐解いていきますので、まずはふんわりと、
「あるまとまった処理を持つコードの塊をクロージャと呼ぶんだなー」
というイメージで読み進めていただいて大丈夫です👌
クロージャと関数は似たような書式を持っていますが、「自身に名前が付いているか」という点に違いがあります。
関数は定義する時に名前が命名されます。対して、クロージャは自身に名前を持ちません。
この特徴から、クロージャのことを「無名関数」と呼ぶこともあります。
【関数の書式例】
・func 関数名(引数…){}と記述して定義する
・定義時に名前が命名される(例では「greet」)
// 挨拶メッセージを返却する関数
func greet(name: String) -> String {
return "こんにちは、 \(name)!"
}
【クロージャの書式例】
・まとまった処理を{}で囲み、変数に代入するなどして使う
・クロージャ自身は名前を持っていない(無名関数)
// 挨拶メッセージを返却するクロージャ
// クロージャ自身は名前を持っていない
{ (name: String) -> String in
return "こんにちは、 \(name)!"
}
// 変数にクロージャを代入するパターン
let greet = { (name: String) -> String in
return "こんにちは、 \(name)!"
}
まずは、クロージャの基本書式を確認しておきましょう。
💡私たちが普段目にしているクロージャは、多くの場合、省略形によって簡潔に記述されています。クロージャ本来の書式は、以下のような形で構成されています。
省略形については、後述【クロージャの省略形】で解説しています。
// クロージャの基本書式
{ (引数名: 引数のデータ型) -> 戻り値のデータ型 in
// 実行したい処理
return 戻り値
}
クロージャでは「in」の次に実行したい処理を書きます。
この基本書式に当てはめて、例えば次のようにクロージャを定義できます。
Int型の引数を受け取り、引数に2を掛け算して返却するシンプルなクロージャです。
// 基本書式に則ったクロージャの定義
// 変数にクロージャを代入するパターン
let sampleClosure = { (number: Int) -> Int in
return number * 2 // 引数に2を掛けて返す
}
クロージャを実際に使用したコードの全体像を以下に挙げます。
クロージャに戻り値がある場合は、例のようにして新しい変数に戻り値を格納することができます。
【全体のコード】
クロージャは、「引数」や「戻り値(返り値)」を持たないクロージャとして定義することも可能です。
// 「()」は引数無し、「-> Void」は戻り値無しを表す
{ () -> Void in
// 処理
// ...
// return は省略可能
}
// 戻り値の 「Void」 は 「()」 に書き換え可能
{ () -> () in
// ...
}
この場合、処理によって返却する戻り値が存在しないため、returnは省略することができます。
書式に当てはめて、例えば以下のようにクロージャを定義できます。
実行時にデバッグメッセージ「”CodeCandy“」がプリント出力されるクロージャです。
/// 「引数」や「戻り値」を持たないクロージャ
let voidClosure = { () -> Void in
print("CodeCandy")
}
引数が必要なく、戻り値も無いため、以下の全体コードのvoidClosure()のように、実行時のコードは変数名()のみとなります。
【全体のコード】
クロージャは、ある一定の条件を満たす時、コードの記述を簡略化することができます。
この機能によって、コードを簡素に記述でき、可読性も高めることができます。
以下のサンプルコードを用いて、条件によってクロージャが省略されていく様子を見ていきましょう。
【サンプルコード】
// 引数に2を掛け算して返すクロージャ
let sampleClosure = { (number: Int) -> Int in
return number * 2
}
クロージャ内のコードが一行に収まっている時、returnを省略できます。
// 条件: クロージャ内の処理が一行である場合
let sampleClosure = { (number: Int) -> Int in
number * 2 // ⬅︎ returnを省略
}
戻り値の型推論が可能な場合、戻り値の型表記を省略できます。
これで「-> Int」を省略することができました。
// 条件: 戻り値が型推論可能である場合
let sampleClosure = { (number: Int) in // ⬅︎ 「-> Int」を省略
number * 2
}
引数の型推論が可能な場合、引数の「()」と型表記を省略できます。
以下のように、引数名だけを記述する形となります。見覚えのある形になってきました。
// 条件: 引数が型推論可能である場合
let sampleClosure = { number in // ⬅︎ 「()」と型表記を省略
number * 2
}
上記までの省略条件が揃っていれば、さらに引数自体を省略することもできます。
「name in」の部分が省略されます。よく見る書き方ですね!
またこの場合、クロージャ内で引数の値を扱うには「$(ダラー)」という記号を使います(後述で解説)。
// 引数自体を省略できる
let sampleClosure = { // ⬅︎ 「name in」 を省略
$0 * 2 // 「$0」は第一引数を表す
}
// このような形でも記述される
let sampleClosure = { $0 * 2 }
「$(ダラー)」ってなに?
ここで新しく出てきた「$0」という記述ですが、これは第一引数のことを表しています。
「$」はSwiftにおいて複数の意味で用いられますが、クロージャの中で使用した場合、省略されている引数にアクセスすることができます。
例えば以下のコードの場合、Int型の第一引数「10」を、「$0」によってクロージャ内で参照しています。
// 「$0」は、第一引数を表す
let sampleClosure = {
$0 * 2 // 第一引数に2をかける
}
let number = sampleClosure(10)
print(number) // 出力結果: 20
複数の引数がある場合は、「$1」で第二引数、「$2」で第三引数…といったように指定参照することが可能です。
// 「$1」で第二引数、「$2」で第三引数...というように引数を指定参照できる
let sampleClosure = {
let a = $0 * 2 // 10 * 2
let b = $1 * 2 // 5 * 2
let c = $2 * 2 // 2 * 2
return a + b + c
}
let number = sampleClosure(10, 5, 2) // 複数の引数を渡す
print(number) // 出力結果: 34
では、省略する前と後のクロージャ書式を見比べてみましょう。
// 省略前
let sampleClosure = { (number: Int) -> Int in
return number * 2
}
// 省略後
let sampleClosure = { number * 2 }
このようにしてクロージャの記述を省略することで、コードの可読性を高めることができます。
省略形は大変便利な機能である反面、「このコードってなんでこうなってるの…?」 となる要因にもなりやすいです。
多くのクロージャは省略形によって使われているので、早めに慣れてしまいましょう👌
Swiftが提供するクロージャの省略形の一つとして、トレイリングクロージャ(Trailing Closure)という省略構文があります。
機能の概要を以下に挙げます。
【トレイリングクロージャの機能概要】
【条件】
・イニシャライザの末尾(最後)の引数がクロージャである場合
【機能】
・引数のクロージャを「()」の外に移動できる
・クロージャの引数名を省略できる
・他に引数が存在しない時、「()」を省略できる
具体的なコードを使って、トレイリングクロージャの動きを確認してみます。
以下は、SwiftUIのButton
の基本書式の一つです。
上記のイニシャライザには、「action:」と「label:」二つの引数がありますね。そして、どちらの引数もクロージャであることがわかります。
Appleのドキュメントから、上記のButton
のイニシャライザ構成を見てみます。
【Buttonのイニシャライザ】
一行として見てみると、イニシャライザの構成が掴みやすいですね。
この場合、末尾の引数は「label:」であり、引数のデータ型「() -> Label」はビューを返却するクロージャの型です。
よって、「末尾の引数がクロージャである」というトレイリングクロージャの適用条件を満たしています。
では、実際にButtonに対してトレイリングクロージャを適用し、コードの変化を見ていきます。
末尾のクロージャ引数「label:」に注目してください。
右記のトレイリングクロージャでは、引数ラベルが省略され、クロージャが「()」の外に移動しているのが見えるでしょうか。
このように、末尾のクロージャ引数のコードを簡略化して、可読性を高めることができるのがトレイリングクロージャのメリットです。
Swift5.3(Xcode12)からの新機能として、マルチトレイリングクロージャ(Multiple Trailing Closure)という省略構文が使えるようになりました。
機能の概要を以下に挙げます。
【マルチトレイリングクロージャの機能概要】
【条件】
・引数にクロージャが連続する場合
【機能】
・クロージャ引数全てを「()」の外に移動する
・最初のクロージャのみ従来の省略記法を適用する
・後続のクロージャはラベルを付与した上で表現する
・クロージャ以外の引数が存在しない時、「()」を省略できる
条件に挙げている「引数にクロージャが連続する」というパターンは、まさに先述で使用したSwiftUIのButton
が該当しますね。
では、Buttonに対して「トレイリングクロージャ」「マルチトレイリングクロージャ」それぞれのパターンを適用し、書式の比較をします。
右記のマルチトレイリングクロージャでは、第一引数「action:」のラベルが省略されて、以降の引数である「label:」がラベル表記されていますね。
さらに、イニシャライザにクロージャ以外の引数が存在しないため、「()」も省略されていることがわかります。
これで、全体のコードをよりスッキリさせることができました!
今回は主にSwiftUIのButton
を使いましたが、使用するプログラムによって適切な省略形は変わってくるので、その都度読みやすい省略形を選択していくのが良いかと思います。
クロージャは、変数に代入して使うことができます。
ここまでの解説でも活用していたパターンですね。
// クロージャを変数に代入するパターン
let greetClosure = { (name: String) -> String in
"こんにちは、 \(name)!"
}
// クロージャの実行
let message = greetClosure("太郎")
print(message) // 実行結果: こんにちは、太郎!
クロージャは、関数の引数として渡すこともできます。多くのプログラムでも用いられている方法です。
クロージャを引数として持つ関数は、高階関数(Higher-order function)とも呼ばれたりします。
【クロージャを持つ関数の書式】
// 引数にクロージャを持つ関数
func 関数名(引数名: クロージャの型) {
// 処理...
}
例えば以下のコード例では、関数の引数のデータ型に「() -> Void」を設定しています。
このように定義しておくことで、関数実行時にクロージャを渡せるようになります。
// クロージャを引数に持つ関数の定義
func sampleFunction(closure: () -> Void) { // ⬅︎ 引数にクロージャの型を定義する
closure() // 受け取ったクロージャを実行
}
// 使用例
sampleFunction(closure: { () -> Void in
print("CodeCandy")
})
// 使用例(省略形)
sampleFunction {
print("CodeCandy")
}
関数実行側で記述したクロージャが、引数として関数内に渡っていくイメージが掴めるとOKです。
【全体のコード】
以上、Swiftにおける「クロージャ」についてでした!
要点をまとめておきます。
・「{ }」で囲まれた、まとまったコード処理を実行できる
・条件下でコードを簡潔にできる(省略形)
・自身に名前を持たない(無名関数)
・変数への代入や、関数の引数として渡すことが可能
書式に少しクセがあったり、様々な省略形があったりで、「このコードってどういうこと…?」となりやすいクロージャですが、プログラミングにおいて無くてはならない超重要な機能です。ぜひ本記事を元に、使い慣らしていきましょう👌
参考になりましたら幸いです。
\ SHARE /
アプリ開発が学べる勉強会を開催中!
CodeCandyではアプリ開発を学ぶための勉強会を定期開催しています。
学習する習慣を身につけたい、他の参加者と作業したい、アプリ開発の基本をマスターしたい、という方のために無料で学べる勉強会です。
グループにメンバー登録して頂くと、イベント開催時にメールで通知されます。
徹底した基礎学習からマスターするiPhoneアプリ開発集中オンライン講座開講!
本書「iPhoneアプリ開発集中講座」を執筆している現役エンジニア講師陣が直接に指導!
基礎、課題実習で実践力を鍛えて、オリジナルアプリ公開までチャレンジ!
充実した転職支援もあるので、エンジニアへ転職したい人にもおすすめです!
まずは、現役エンジニアに相談できる無料相談をご利用ください。
2022年2月よりSwift学習を始め、4月からiOSアプリ開発オンラインスクール「CodeCandy」にてアプリ開発を学ぶ。 2023年10月に個人開発アプリ「unico」をリリース。現在はアプリの機能アップデートをしながら、スクール運営の技術ブログの執筆や、出版書籍の入稿チェック・デバッグにも携わる。