35
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

The Swift Programming Language - Closures(クロージャー)をまとめる

Posted at

The Swift Programming Language をまとめるトップ

Closures(クロージャー)

C言語やOjbCのブロックや他lambdas(ラムダ)に類似している
キャプチャの所は丁寧にやった

The Sort Function(ソート関数)

Swiftには配列をソートする sort 関数がある
sort が完了したら、以前の配列のサイズと型が同じ新しい配列を生成する
sort 関数の引数は二つ

  • 第一引数は配列
  • 第二引数はクロージャーで引数を二つ取り戻り値は Bool

sort(Array, (String, String) -> Bool)

以下の、文字列配列を降順にソートしてみる

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

func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
}
var 降順 = sort(names, backwards)
// 降順 の中身 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

Closure Expression Syntax(クロージャー書き方)

一般的な書き方

{ (引数,,,) -> 戻り値型 in
     ステートメント
}

例えば

降順 = sort(names, { (s1: String, s2: String) -> Bool in
    return s1 > s2
    })

Inferring Type From Context(型の推論)

Swift は引数の型など推論できる場合がある
型や(->)も省くことができる
読み手に分かりやすくしたい時は、明示したほうが良い場合もある

降順 = sort(names, { s1, s2 in return s1 > s2 } )

Shorthand Argument Names(簡易引数名)

引数を参照する為の $0, $1, $2 等を自動に提供する
もっと手軽に短く書ける

降順 = sort(names, { $0 > $1 } )

Operator Functions(演算子関数)

更に短く書ける

降順 = sort(names, >)

Trailing Closures(後置記法)

関数の最後の引数は()の外に出す事ができる
{} ブロック内が長い場合など便利

func someFunctionThatTakesAClosure(closure: () -> ()) { // function body goes here }
 
// これが後置記法で無い場合
someFunctionThatTakesAClosure({ // closure's body goes here })
 
// これが後置記法の場合
someFunctionThatTakesAClosure() { // 後述 closure's body goes here }

後置記法で sort 関数は以下の様にも書く事ができる

reversed = sort(names) { $0 > $1 }

Trailing Clouser はクロージャー内のコードが長いときに便利
配列は map メソッドをもっていて、Tailing クロージャーに対応
サンプルソースのみ紹介

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
let strings = numbers.map {
    (var number) -> String in
    var output = ""
    while number > 0 {
        output = digitNames[number % 10]! + output
        number /= 10
    }
    return output
}
// strings は String[] として推論される
// 値の中身 ["OneSix", "FiveEight", "FiveOneZero"]

Capturing Values(キャプチャ値)

クロージャーはそれが定義された周辺の定数や変数を捉えて操作(キャプチャ)することができる
クロージャーの文中でそれらの定数や変数の値を参照したり修正したりすることができる
その定数や変数が元のスコープ(つまりそのクロージャー文中外)で定義されて、それが既に破棄されていても文中内で値を参照したり操作できる
キャプチャ値によって、変数や定数をコピーしたり参照操作したりすることができる

一番シンプルクロージャーは関数の文中にあるネストされた関数
ネストされた関数はそのネストされた関数内の範囲で、ネストしている関数の引数や他あらゆる定数や変数を捉えて操作することができる

incrementor というネストされた関数をもつ makeIncrementor という関数を使った以下の例をみてみる
ネストされた incrementor 関数が周辺にある二つの値 runningTotalamount を捉えて処理している
その処理後、 incrementormakeIncrementer が毎回呼ばれるたびに runnintTotal の数値を加算するクロージャーとして返される

func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

makeIncrementor() -> Int 型を戻り値とする
つまり、 値を戻り値とするということではなく、関数を戻り値とする ということ
この関数が呼ばれる毎に、引数が指定されていない Int を戻り値とする関数を返す
関数がどんな関数を返すかは、戻り値としての関数型を見る必要がある

makeIncrementor 関数では runningTotal 変数を定義し現在の running total を保持し incrementor によって返される
この runnintTotal は 0 で初期化されている

makeIncrementor 関数は forIncrement という外側で引数を渡すときに分かりやすくするために付けられた名前が指定されているInt型の外部引数をもっていて、関数内では amount としてローカルの名前を使って操作できる
この引数は、戻り値となる incrementor 関数が呼ばれるたびにどれくらい加算するか保持している

makeIncrementorincrementor というネストされた関数を文中で定義し、それが実際の加算処理を行う
この関数は単純に runningTotal 変数に amount を加えて、結果として返す処理をするだけ

incrementor 関数だけ見た場合、少し不自然

func incrementor() -> Int {
    runningTotal += amount
    return runningTotal
}

incrementor だけみると関数は引数がなく、{} 文中に runningTotalamount として定義されたものはなく外側で定義されたものということが分かる

外側の関数の amount 引数は修正ができないので、 incrementor はその値を参照し新たな amount に複写保存する
そして、この amountincrementor 関数内でのみ有効となる

incrementor が呼ばれる毎に runningTotal 変数が更新される
incrementor 内の runningTotal は外側で定義された runningTotal を参照しているだけで初期化された複写ではない
また、 makeIncrementor が呼ばれる時に runningTotal が破棄されない様に参照し、次に incrementor 関数が呼ばれる時に永続して利用できるよにしている

実際の処理

let incrementByTen = makeIncrementor(forIncrement: 10)

incrementor 関数型の関数 incrementByTen を引数 10 で定義し実行してみる

incrementByTen()
// 戻り値 10
incrementByTen()
// 戻り値 20
incrementByTen()
// 戻り値 30

別の incementor をつくってみる

let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
// 戻り値 7
incrementByTen()
// 戻り値 40

NOTE
クロージャーをクラス属性(property)にアサインしたり、そのクラスのインスタンスやメンバー変数に参照されたクロージャーの場合、強参照で参照される
Strong Reference Cycles for Closures を参照すべし

Closures Are Reference Types(クロージャー参照型)

上記の例の incrementBySevenincrementByTen定数 である
これらのクロージャーは参照型で、定数が参照してるクロージャーは runningTotal 変数を永続的に保持操作できる

関数やクロージャーを定数や変数に定義するたびに、実際は関数やクロージャーを参照している定数や変数を定義していることになる
incrementByTen はただの定数で、クロージャーそのものではない

以下は2つの異なる定数か変数を定義した時、それぞれ同じクロージャーを参照していることがわかる

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 戻り値 50
// incrementByTen() と同じクロージャーを参照している
35
36
0

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
35
36

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?