はじめに
初めてカンファレンスに参加した時、登壇者の口から初めて聞いた無名関数。
あの日から今の今までその意味もどんなものかも知りませんでしたが...
今日その実態がわかったので共有します。
普通の関数
まずは皆さんがよく使っている名前付きの関数について今一度見てみましょう。
func greet() {
print("Hello")
}
これはgreetという名前の関数ですね。
呼び出すときも名前で呼ぶことができますね。
greet()
名前がないだけで関数ではある
では今回の主役無名関数がどのようなものかというと
let greet = {
print("hello")
}
一見すると「処理を定数に入れてるだけ」に見えますよね。
でもこれ、よく見ると { ... } の中身って 関数の中身 と同じでクロージャの中に処理を書いてますよね。
つまり無名関数とはその名の通り 「名前のない関数」 ということです。
{ print("hello") }が「名前のない関数(無名関数)」で、それをgreetという箱に入れています。
呼び出し方が同じ
この定数greetを使うときは普通の関数と同じように()をつけて呼び出せます。
greet() // 出力: hello
.......普通の関数でよくね????
私もそう思いました。
では実際無名関数とはどんなところで便利なのでしょうか?
関数の引数に処理を入れられる
Buttonを例にしてみてみましょう。
SwiftUIでよく見るこの書き方
Button("押してね") {
print("押された!")
}
よくよくみると意味がわからなくないですか?
結論から言うと、ButtonにはString型のラベルだけではなく、 無名関数が引数として渡されています。
普通に引数って()の中に書かれますよね。
実はこのButtonの書き方って省略形なんです。
本当はこのように書かれているのと同じ意味です。
Button("押してね", action: {
print("押された!")
})
こうみると引数に文字列と処理が入っていることがわかりますね。
Swiftには、最後の引数が無名関数なら、()の外に出して書いてOK という便利なルールがあって、この書き方を 末尾クロージャ と呼びます。
なのでaction:を省略してこう書けるのですね。
Button("押してね") {
print("押された!")
}
もし無名関数がなかったら?
もし無名関数がなく、全て名前付き関数で定義しなければならなくなった時どうなってしまうのでしょうか?
例えば画面にボタンが5個あった時はこうなります。
// 1. まず、ボタンごとの処理を全部「関数」として定義
func save() { print("保存しました") }
func delete() { print("削除しました") }
func cancel() { print("キャンセルしました") }
// 2. それを一つずつセットする
Button("保存", action: save)
Button("削除", action: delete)
Button("キャンセル", action: cancel)
めちゃくちゃめんどくさそうだしボタンがどんな操作をするのかボタンを見ただけではわからないですよね。
また、ボタンが増えるほど関数を先に定義しなければならなかったりで動きはするけど周りくどいコードになってしまいます。
一方で無名関数が使えると
Button("保存") {
print("保存しました")
}
Button("削除") {
print("削除しました")
}
Button("キャンセル") {
print("キャンセルしました")
}
このようにめちゃめちゃスッキリしました。
コードの可読性も一気に上がりましたね。
引数に「処理」を渡せる
さっき Button の例で「処理(無名関数)が引数として渡されている」ことが分かりました。
じゃあ Button だけ特別なのかというと、そうではなくて、Swiftでは普通に
関数の引数として “処理そのもの” を受け取れる
ようになっています。
例えば、引数でもらった処理を実行するだけの関数を書いてみます。
func run(_ action: () -> Void) {
print("run開始")
action()
print("run終了")
}
action: () -> Void は「引数なし・戻り値なしの処理」という意味です。
これを呼び出すときに {} を渡せます。
run({
print("ここが実行された!")
})
ここでも末尾クロージャで省略できるルールを使用できこのようになります。
run {
print("ここが実行された!")
}
この時Swift側がrunの直後に{}が来ているので{}が最後の引数だと自動で判定してくれて、()を省略できています。
Swiftちゃん頭良すぎるっぴ
実行結果はこうなります
run開始
ここが実行された!
run終了
初心者の私にはこれを最初見たとき全く理解できませんでしたが、末尾クロージャの概念を知って「最後の引数(クロージャ)を () の外に出して書けるだけなんだ」と分かり、腑に落ちました。
引数に処理が渡せると嬉しいこと
私的に一番大きいのが 処理を使い捨てできる ということです。
func で関数を定義するほどでもない「1回しか使わない関数」や「使い回さない関数」って結構あると思います。
そういう時に {} をその場で渡せば
- 事前に関数を作らなくていい
- コード量が減る
- “このボタンは何をするか” がその場で読めて可読性が上がる
という感じで便利です。
クロージャ
ここまでずっと「無名関数」と呼んできましたが、Swiftでは 無名関数 または {...}のことを クロージャ(Closure) と呼んでいるようです。
なので末尾クロージャと言ったりするのですね。
まとめ
以上無名関数もとい、クロージャについて解説してみました。
ここまで読んでくださった皆様、ありがとうございます。
ButtonやDispatchQueue.main.async、VStackでも初期化の際に、使われているようで、知らず知らずのうちに使いまくっていたなんて
「クロージャよ、今まで知らなくてごめん!コードを楽にしてくれてありがとう!」
という気持ちになりました。
皆さんもクロージャを使ってより良いSwiftライフを過ごしてみてはいかがですか?