はじめに
Swift学習を始めた皆さまがぶつかる問題の1つがクロージャの扱いだと思います。
例に漏れず私もクロージャの扱いに苦戦していました(というか今も練習中です)。
「クロージャ分からん!!!🤯」 となる壁の1つは、下記のようなコードではないでしょうか。
func greetUser(greeting: (String) -> String) {
let message = greeting("こむぎ")
print(message)
}
greetUser { name in
return "Hello, \(name)!"
}
こちらを理解できるように、順番に解説していきます。
「クロージャは無名関数」
タイトルの通りですが、こちらの文言を聞いたことがありますでしょうか。
検索するとこのように説明されている記事が多くヒットします。
理由は下記のコードを見るとわかりやすいかと思います。
let greeting = { (name: String) -> String in
return "Hello, \(name)!"
}
print(greeting("こむぎ")) // 出力: Hello, こむぎ!
name
という引数を受け取った後にHello, {name}!
の形式で値を返しており、メソッド(関数)の動きとよく似ています。
私自身、冒頭に述べたコードは難しいのにこちらは理解できていました。不思議ですね...🧐
実は、理解が難しくなってしまう原因はクロージャをメソッドの引数として渡すことです。
文字だとわかりづらいので、例を見てみましょう。
クロージャをメソッドの引数として渡す
よくクロージャ渡しと言われるヤツです。これが厄介です。
プログラミング初期では、メソッドの引数にInt
やString
などの値を表す型を渡す場合が一般的です。しかし、Swiftではクロージャを引数として渡すことができてしまいます。
型名はクロージャが({受け取ったデータ型}) -> {返り値のデータ型}
で表現されます。
例えば、先ほどのコードをメソッドに渡す場合は下記のようになります。
// メソッドの定義
// greetingメソッドは文字列を受け取って文字列を返すので (String) -> String と定義する
func greetUser(greeting: (String) -> String) {
let message = greeting("こむぎ") // 1, 2
print(message) // 3
}
// クロージャの定義
let greeting = { (name: String) -> String in
return "Hello, \(name)!" // 2
}
// メソッドにクロージャを渡して呼び出し
greetUser(greeting: greeting) // 出力: Hello, こむぎ!
ここで初めて登場したgreetUser
は、先ほどのgreeting
クロージャから返った文字列を出力する役割を持っています。
処理の手順
- 引数として受け取ったクロージャ
greeting
に"こむぎ"
を渡して実行 - クロージャから返ってきた結果を変数
message
に格納 -
print
メソッドでmessage
を出力
このように、クロージャ渡しをすることで、クロージャの中身が別のメソッドで実行されます。
形は異なりますが、メソッドとほとんど同じ動きをします。
上記のコードでクロージャを引数として渡すイメージは掴めたでしょうか。
クロージャの省略表記
では、次にクロージャの省略表記をした場合のコードを見てみましょう。
// メソッドの定義
func greetUser(greeting: (String) -> String) {
let message = greeting("こむぎ")
print(message)
}
// メソッドにクロージャを渡して呼び出し
greetUser { (name: String) -> String in
return "Hello, \(name)!"
}
もうなんのこっちゃですね。
私が最初見た時には、
「greetUser
がメソッドなのに、なんで処理の内容が続きに書かれてるんだ?どっちを先にやればいいか分からんなぁ...そもそもname
ってどこから来るんだ??」
となりました。
省略記法に関してはこちらの記事がめちゃくちゃ分かりやすく説明してくれているのでぜひご覧ください!
動きとしては先ほどのコードと同一なので、8行目以降のコードはgreeting(こむぎ)
が呼び出された時に実行されます。下記のようなイメージを持っていただければ分かりやすいと思います。
図で示した部分を一言で表すなら次の通りです。
メソッド名の後に{ }
で囲まれているコードは、引数内のクロージャ引数に格納される
ここでは3行目にgreeting
クロージャが呼ばれており、クロージャに渡した"こむぎ"
という文字列を8列のname
が受け取って処理を続けるイメージです。
さあ、ここまで着いてきたあなた、ありがとうございます。あと少しです。
クロージャの省略表記は、先ほどの例よりもさらに進めることができます。
その結果が下記の通りです。
// メソッドの定義
func greetUser(greeting: (String) -> String) {
let message = greeting("こむぎ")
print(message)
}
// メソッドにクロージャを渡して呼び出し
greetUser { name in
return "Hello, \(name)!"
}
なんと、クロージャ内の引数と返り値表記が消えました。
引数と返り値が無くなると、途端にメソッドと似たような動きであることが分かりづらくなります。
これを違和感なく読めるようになれば、冒頭のコードも読めることでしょう。
既にお気づきの方もいるかもしれませんが、こちらのコードは冒頭と同じです。
読み始める前と比較して、クロージャの動きがイメージできるようになってきましたでしょうか?
「クロージャ分からん!!!🤯」 と噴火している方々を1人でも鎮火させられていれば幸いです。
この記事がお役に立ったと感じる方は、❤️と私のX(Twitter)にコメントを残していただけますと喜びます🙌
ではでは。