iBooks にある “The Swift Programming Language” の勉強メモ。Objective-C と C を普段書いている自分から、ちょっと馴染みがないものを特にまとめておきます。目次は こちら。
今回は、Trailing Closure に関して。
Trailing Closure
Trailing Closure は、Closure を関数の最後の引数として渡し、さらにその Closure が長くなるような場合に役に立ちます。Trailing Closure は、関数の () の書きます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | func someFunctionThatTakesAClosure(closure: () -> ()) { // function body goes here } // here's how you call this function without using a trailing closure: someFunctionThatTakesAClosure({ // closure's body goes here }) // here's how you call this function with a trailing closure instead: someFunctionThatTakesAClosure() { // trailing closure's body goes here } |
このコードは、Trailing Closure を使用したシンタックスと使用していないシンタックスを比較しています。最初に、Closure を受け取る関数があります。そして、Closure を関数呼び出し時に () の中に書く例と、() の後に書く Trailing Closure の例です。
以前 の Sort 関数の場合にはこのように書きます。
1 | reversed = sort(names) { $0 > $1 } |
この例では、あまり役に立ちませんが、Closure が長くなった時にちょっと便利です。
7. 配列の map 関数の Trailing Closure の例
配列の map 関数は、引数の Closure を配列の各要素に対して実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 is inferred to be of type String[] // its value is ["OneSix", "FiveEight", "FiveOneZero"] |
もし、Closure が関数のただ 1 つの引数として与えられ、Trailing Closure を使用する場合には、通常、関数名の後につける () を省略することができます。この map 関数はまさにこの例です。map 関数の後、直接 closure が始まっています。
また、ここで digitNames[number % 10]! と、Dictionary の要素を呼ぶ際に、! が付いています。これは、Dictionary の subscripts は、Optionals を返すためです。そして、forced unwrap しています。
8. Capturing Values
Closure は、変数とコンスタントをその文脈においてキャプチャできます。たとえ元の値を定義した範囲が既に存在しなくとも、Closure の本体で、そのキャプチャした値を変更できます。
一番イメージしやすいのは、ネストされた関数です。ネストされた関数は、ネストしている関数の引数、その本体で定義されている変数とコンスタントをキャプチャできます。
1 2 3 4 5 6 7 8 | func makeIncrementor(forIncrement amount: Int) -> () -> Int { var runningTotal = 0 func incrementor() -> Int { runningTotal += amount return runningTotal } return incrementor } |
この例では、makeIncrementor 関数が incrementor 関数をネストしています。そして、incrementor 関数は、makeIncrementor の引数である amount と、runningTotal をキャプチャし、runningTotal の値を変更しています。
Swift は、キャプチャする際に、どのようにキャプチャするかを自動的に判定してくれます。この例において、amount は、incrementor 関数の中で変更されません。そのため、amount は、この amount の値のコピーを incrementor 関数 で保存しています。それに対して、runningTotal は、incrementor 関数の中で変更されています。そのため、もしこの runningTotal のコピーしても、変更が incrementor 関数が終了した際に、反映されません。そのため、この runningTotal は、参照として渡されます。私たちプログラマーは、これらのことを気にすることなく、ただキャプチャして使用して問題ありません。
9. Closure は参照
関数と Closure は、参照型です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | let incrementByTen = makeIncrementor(forIncrement: 10) incrementByTen() // returns a value of 10 incrementByTen() // returns a value of 20 incrementByTen() // returns a value of 30 let incrementBySeven = makeIncrementor(forIncrement: 7) incrementBySeven() // returns a value of 7 incrementByTen() // returns a value of 40 |
この例では、incrementByTen と incrementBySeven はともにコンスタントです。しかし、両者とも runningTotal を変更することができています。これは、関数と Closure が、参照型だからです。
1 2 3 | let alsoIncrementByTen = incrementByTen alsoIncrementByTen() // returns a value of 50 |
また、Closure が参照ということは、違うコンスタントや変数に Closure を代入した際に、これらの変数やコンスタントは同じ Closure を指していることを意味します。上記の例では、alsoIncrementByTen コンスタントは、incrementByTen が指している Closure と同じ Closure を指しています。