[Swift]クロージャーについてまとめてみたよ

追記

構成を直して書き直しました。
こちらからご覧になれます。


Objective-CからSwiftに乗り換え中ですが、つまづく文法がいろいろあります。
その中の一つがクロージャー。
今回はクロージャーについてまとめてみました。

そもそもクロージャーって?

wikiの説明を見てみます。

クロージャ(クロージャー、英語: closure)、関数閉包はプログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数で実現している。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。

wiki-クロージャ

クロージャーはざっくり言うと自分を囲むスコープにある変数を操作できる関数です。

以下のようにネストする関数を考えてみます。

func outer() -> () ->Void{
    
    var x = 10
    
    func inner(){
        
        print("X is \(x)")
        x  = x + 1//inner関数で定義されていないx変数を操作している。
    }
    return inner//関数を返り値として返している
}

var f = outer()

f()//-> X is 10
f()//-> X is 11
f()//-> X is 12


上の例ではouter()関数は() ->Void(引数がなくて、戻り値もない)関数を返す関数です。
Swiftは第一級関数をもつプログラム言語なので、関数も値と同じように扱えるので、関数を返す関数も定義できます。

outer()の中にネストする形でinner()関数を定義しています。
このinner()ではouter()で定義されたXという変数をそのまま操作しています。

    func inner(){
        
        print("X is \(x)")
        x  = x + 1//inner関数で定義されていないx変数を操作している。
    }



このように、自分を囲むスコープにある変数を操作できる関数をクロージャーといいます。

参考
なぜクロージャ(Closure)と言うのか?


また、クロージャーは自身が定義されたコンテキストから定数や変数をキャプチャして、保存する特徴もあります。

Appleで配布されているiBooksのThe Swift Programming Language (Swift 2.1)では、一般的にクロージャーは以下の3種類の形態があるそうです。

  • グローバル関数はある名前を持ち、変数を保持していないクロージャーの一種
  • ネストされた関数はある名前を持ち、ネストしている関数の変数を保持しているクロージャーの一種
  • クロージャーは名無しのクロージャーとして表現できる。他の関数の変数を操作できる

グローバル関数もクロージャーの一種ということは、どんな関数もクロージャーということですね。
ここらへんは定義の問題なのであんまり深入りしないほうがいいかも。

加えて、Swiftのクロージャーの特徴は

  • 文脈から引数と戻り値の型を推論する
  • Closre Body が単一式の場合はそれが暗黙の戻り値になる
  • 引数の名前を略式でかける
  • Closure を最後の引数として取るメソッドの場合、Closure を () の外側に書くことができる(Trailing closure )

とのことです。

Swiftのクロージャーはいろいろと略式で記述ができるので、初めて見ると戸惑うかもしれません。が、1つずつどこが略されているのかを見ていけば理解できると思います。


Swiftのクロージャーの文法

Swiftのクロージャーは以下のように書きます。

{ (引数) -> 戻り値の型 in
  処理
}

sortメソッドでクロージャーを見てみよう

swiftの標準ライブラリーのsort(_:)メソッドを使ってクロージャーの書き方を見てます
sort(_:)メソッドは配列に対してソートした配列を返してくれるメソッドです。
sort(_:)の引数には関数を入れられます。関数の引数は、配列の要素である二つの引数を持ち、真偽値を戻り値として返します。
最初の引数が現れて、次に二番目の引数が現れるときにtrueを返しそれ以外はfalseを返します。

名前つき関数で実行

sort(_:)メソッドを名前つき関数で実行してみます。

let name = ["Chris", "Alex", "Ewa", "Barry", "Daniella"]

/*
関数を定義してみる
*/
func backwards(s1: String, _ s2: String) -> Bool{
    return s1 > s2
}

var reversed = name.sort(backwards)

print(reversed)// ->["Ewa", "Daniella", "Chris", "Barry", "Alex"]

String型の引数を2つ持ち、Bool型を返す関数backwards(s1: String, s2: String)を定義します。
処理はreturn s1 > s2で1番目の引数と2番目の引数を比較して1番目が大きかったらtrue、それ以外はfalseを返します。

func backwards(s1: String, _ s2: String) -> Bool{
    return s1 > s2
}

実行するとname配列の中身がアルファベット順で大きい順番にソートされます。


var reversed = name.sort(backwards)
print(reversed)// ->["Ewa", "Daniella", "Chris", "Barry", "Alex"]


クロージャーで実行してみる

次にクロージャーを使ってsort(_:)メソッドを実行してみます。


reversed = name.sort({ (s1:String, s2: String) -> Bool in
    return s1 > s2
})

print(reversed)// ->["Ewa", "Daniella", "Chris", "Barry", "Alex"]


sort(_:)の引数に以下のクロージャーが入っています。

{ (s1:String, s2: String) -> Bool in
    return s1 > s2
}

型推論

Swiftのクロージャーは引数と戻り値の型を推論してくれます。
name配列の方は[Srting]String型の配列ということがわかっているので、引数の(s1:String, s2: String) は省略できます。

同様にs1 > s2と書けば返り値はBool型なのも推論できるので、返り値の -> Boolも省略できます。

なので以下のようにも書けます。

/*
Swiftは型を推定。
配列の型は推定できるから(String, String) -> Boolの記述は省略できる
*/
reversed = name.sort({
    s1, s2 in return s1 > s2
})

print(reversed)

暗黙の戻り値

Swiftのクロージャーは戻り値を暗黙に返します。
なので次のようにreturnを省略できます。

/*
処理を単式で書いた場合は戻り値は暗黙に返される。なので'return'の文字はいらない
*/

reversed = name.sort({s1, s2 in s1 > s2})
print(reversed)

略式の引数

Swiftのクロージャーの引数は略式で書けます。
$0,$1,$2というように自動的に連番されています。
この略式を使うと、inの文法も省略できます。

/*
略式の引数名
クロージャーには自動的に引数に名前がつけられている
$0,$1,$2
これを使うと、引数の定義をしなくてもいいし、 inの文字もいらない
*/
reversed = name.sort({ $0 > $1})
print(reversed)

オペレーター関数を使う

最後に、ここまで来るかという省略方法。
SwiftのStringオペレーター関数の>が使えます。
オペレータ関数>は2つのString型の引数を渡すと戻り値としてBool型の変数を返します。

それを使ったクロージャーの式が以下です。

/*
オペレーター関数を使う
SwiftのString型は > が使える。 > は2つのString型引数をもち、戻り値としてBool型を返す。
sort(_:)にぴったり
*/
reversed = name.sort(>)
print(reversed)

とうとうクロージャーがの記述が>だけになってしまいました。。
これをいきなり見てもうろたえないようにしましょう!!

Trailing Closures

SwiftのクロージャーにはTrailing Closuresという書き方もあります。
関数の引数の末尾(最後)がclosureを受け取る場合、処理部分を外に配置できるというものです。
これを使うと、コードの可読性があがって、見やすいコードになります。

// 「(Int, Int) -> Int」という型の関数を受け取る
func add(a: Int, b: Int, closure: (Int, Int) -> Int) -> Int {
    return closure(a, b)
}

// addを普通に使う
add(1, b: 5, closure: { (a: Int, b: Int) -> Int in
    return a + b
})

// Trailing版
// 実際の処理を引数の()の外に置いている
add(1, b: 5) {
    $0 + $1
}

add関数はTrailing Closuresを使わないと以下のように見づらい記述になります。

add(1, b: 5, closure: { (a: Int, b: Int) -> Int in
    return a + b
})

それがTrailing Closuresを使うとすっきりします。

// Trailing版
// 実際の処理を引数の()の外に置いている
add(1, b: 5) {
    $0 + $1
}

sort関数でTrailing Closures

sort(_:)でもTrailing Closuresを使ってみます。

reversed = name.sort(){
    $0 > $1
}
print(reversed)

Trailing Closuresで、引数がクロージャー以外なかったら()を省略できるそうです。
なので以下のようにも書けます。

/*
Trailing Closuresで、引数がクロージャー以外なかったら()を省略できる
*/
reversed = name.sort{
    $0 > $1
}
print(reversed)

まとめ

Swiftのクロージャーをまとめました。
Swiftのクロージャーは書きやすいように略式で書けますが、初めて見ると戸惑うことも多いと思います。
今回の記事で1つずつ、どこが省略されるのかを示したので、ぜひ参考にしてください!

参考