LoginSignup
0
0

More than 1 year has passed since last update.

Swiftのクロージャが理解できなくて大粒の涙を流した

Last updated at Posted at 2022-11-27

お前は何を言っているんだ

 var filteredLandmarks: [Landmark] {

        landmarks.filter { landmark in

            (!showFavoritesOnly || landmark.isFavorite)

        }

    }

apple公式のSwiftUI Tutrialにこのようなコードがありました。
はい、初心者にはよくわかりません。具体的には

[Landmark] {/*...*/}
landmarks.filter  {/*...*/}

この2つの書き方です。まず、

配列の直後にクロージャってなんだよ!
.filterも、直後にクロージャをもってきて何やってるんだよ!
そもそもクロージャってなんだよ!

私は上記の書き方ではfilteredLandmarksは型を明示しただけで、初期値は入っていないと一瞬思ったのですが、きちんと入っているようですね。そもそも自分の中で変数・定数を宣言する以下の3つの方法のうち、初期値を入れられるのはイコールで左右を結ぶものだけと考えていました。

var a = 1      //型推論
var a:Int = 1  //型明示
var a:Int      //型のみ、初期値なし?

しかし最初の例では宣言にイコールは使われていないものの、型([Landmark])を与え、
その後のクロージャの中で[Landmark]型であるlandmarksから配列のfilterメソッドを使って条件に合う要素だけを取り出し、その新たな配列を返している、ということなのでしょうか?
うーん、クロージャ君、君のことがよくわからんのだよ。

.filter {landmark in
/*...*/}

の中身はFor in文などと似ているので理解できているかと思います。
配列の要素を一つ一つ評価する際、その要素がlandmarkに渡されているんですよね?
(これをイテレーターというのかな?)
そして論理和で、
お気に入りのみ表示するフィルター(showFavoritesOnly:Bool)がfalse
またはその要素がお気に入りされているか(landmark.isFavorite)がtrue
ならtrue。そしてfilterはtrueを返す要素を返す。
そうですよね、ね!

クロージャとは?

そもそもクロージャとは何でしょうか?

Google先生によれば、無名関数です。
あ、なるほどね、無名関数ね。

は?

プログラミングあるある、知らない単語を調べると、別の知らない単語で説明されている。
ただ、いい感じに説明してくれているサイトもありました。

どうやらクロージャ(無名関数)は文字通り「名前のついていない関数」のようですね。

例えば

//クロージャ

let a = {print("テストです")}
a() ///出力:テストです。

//関数
func t(){
    print("テストです。")
}
t() //出力:テストです。


この2つのコードは同じ、どちらも関数なんですね。

基本的な書き方としては

{(引数:引数の型)->返り値の型 in
   処理
return 返り値}

例えば

let closure = {(fruit:String,price:Int) -> String in
    let str = "\(fruit)は\(price))円です。"
    return str}

let casher = closure("グレープ",100)
print(casher) //出力 グレープは100円です。

となります。省略形は複数あり

{()->返り値の型 in ...return 返り値}  //引数なし 返り値あり
{()->返り値の型 in ...返り値}         //処理が一行の場合はreturnを省略できる
{()->Void in ...}                  //引数なし 返り値なし
{()->() in ...}                    //引数なし 返り値なし
{...}                              //引数なし 返り値なし ここまで省略できる。

と表現できます。
クロージャの基礎がわかった現在、もう一度最初のコードのわからなかったところを見直してみます。

[Landmark] {/*...*/}
landmarks.filter  {/*...*/}

うーん、やっぱりわからん!

普通配列やメソッドの直後に関数ぼーんって置いたりしないですよね?
どうしても理解ができず、ググってみたところ、こいつはどうも Trailing closure という記法らしいです。

Closure を最後の引数として取るメソッドの場合、Closure を () の外側に書くことができる>(Trailing closure )
https://blog.personal-factory.com/2016/01/07/how-to-swift-closure/

なるほど、じゃあ本来の書き方なら.filter({})ってことか!
ここで.filterの定義を見てみます。

@inlinable public func filter(_ isIncluded: (Element) throws -> Bool) rethrows -> [Element]

確かにfilterの引数は「Element(要素)を貰いBoolを返すクロージャ」となっていますね!
(throwsはエラー処理らしいが今は知らん!!!)

[Landmark]{}は?
ちょっとここで力尽きました。大粒の涙を流して寝ます。

追記:
うーん、playgroundで配列を以下のように宣言したら、普通にできたんですよね。
つまりこれは宣言の仕方の一種類ってことなのかな?

var abc:[Int]{[1,2,3]}
abc                  //[1,2,3]

ただし

let abc:[Int]{[1,2,3]}
abc                  //[1,2,3]

はできませんでした。つまりまず abc:[Int] ここで型を明示し初期値を入れずに宣言し、その後クロージャで値が渡されている?

ただ、もちろんですが以下はできるんですよね。なんなんだ[]{} この構造!

let abc:[Int ]
abc = [1,2,3]

追記2:

    var landmarkIndex: Int {
        modelData.landmarks.firstIndex(where: { $0.id == landmark.id })!
    }

このような書き方を見つけました。つまりこれは変数の宣言の仕方の一種なんですね。こういうものだと覚えとこう。

var abc:Int{ここがIntを返すクロージャ}

これで初期値を定めた変数abcが宣言できます。

参考:

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0