Capturing Values(キャプチャ)
sample1-1
func createCounter(countBy countBy:Int)->()->Int{ var count:Int=0 return {()-> Int in count += countBy return count } } var counter1=createCounter(countBy: 8) print(counter1()) //8 print(counter1()) //16 print(counter1()) //24 var counter2=createCounter(countBy: 10) print(counter2()) //10 print(counter2()) //20 print(counter2()) //30 print(counter2()) //40 var counter3=counter2 print(counter3()) //50 print(counter3()) //60 print(counter3()) //70 counter2=createCounter(countBy:3) print(counter2()) //3 print(counter2()) //6 print(counter2()) //9 print(counter3()) //80 print(counter3()) //90 print(counter3()) //100
クロージャー(createCounter関数内の赤文字部分↓)は自分自身が定義されているコンテキスト(createCounter内)から定数・変数(変数countと引数countBy)をキャプチャする。定数・変数が定義されているスコープがすでに存在しないとしても(外側の関数の実行が終わっても)、クロージャーは定数・変数を参照・修正できる。
func createCounter(countBy countBy:Int)->()->Int{
var count:Int=0
return {()-> Int in
count += countBy
return count
}
}
Swiftでは値をキャプチャできる最もシンプルな形のクロージャーは他の関数の本体内に定義されたネスト(入れ子)された関数である。ネスト(入れ子)された関数は外側の関数(createCounter)の引数(countBy)と、外側の関数の内側で宣言された定数・変数(count)をキャプチャできる。
func createCounter(countBy countBy:Int)->()->Int{ var count:Int=0 return {()-> Int in count += countBy return count } } var counter1=createCounter(countBy: 8) print(counter1()) //8 print(counter1()) //16 print(counter1()) //24 var counter2=createCounter(countBy: 10) print(counter2()) //10 print(counter2()) //20 print(counter2()) //30 print(counter2()) //40 var counter3=counter2 print(counter3()) //50 print(counter3()) //60 print(counter3()) //70 counter2=createCounter(countBy:3) print(counter2()) //3 print(counter2()) //6 print(counter2()) //9 print(counter3()) //80 print(counter3()) //90 print(counter3()) //100
sample1-1でcreateCounter内のクロージャーが変数countと引数countByを、自身の周りのコンテキストからキャプチャします。countとcountByの値をキャプチャした後クロージャーはcreateCounterにより返り値として返されます。
createCounterにより返されるクロージャーは、呼び出される度にcountの値をcountByだけ増分させるクロージャーです。
createCounterが返す返り値の型は ()->Int です。つまりただの値ではなくクロージャーを返すということです。引数無し、Int型の値を返すクロージャーです。
createCounter関数はcountという名のInt型の変数を定義しています。createCounterが返すクロージャーが呼び出される度に加算される合計値を格納する変数です。変数countは0で初期化されます。
createCounter関数はargument labelがcountBy、parameter nameもcountByであるInt型の引数を一つ取ります。この引数は、createCounterが返すクロージャーが呼び出される度にcountをどれだけ増分させるかを示すInt型の数値です。
createCounter内で定義されるクロージャーが、実際にcountを増分させます。このクロージャーはシンプルにcountをcountByの値だけ増分させます。そして増分後の値を返します。
{()-> Int in
count += countBy
return count
}
クロージャーは引数無しで、自身の内部でcountとcountByを参照します。これは、クロージャーの周囲からcountとcountByへの参照をキャプチャして、それをクロージャー内で使うことで実現しています。参照をキャプチャすることで、createCounter関数の実行が終了した後もcountとcountByは消えず、クロージャーが次に呼び出しされる時にcountが利用できることが保証されます。
sample1-1(再掲)
func createCounter(countBy countBy:Int)->()->Int{ var count:Int=0 return {()-> Int in count += countBy return count } } var counter1=createCounter(countBy: 8) print(counter1()) //8 print(counter1()) //16 print(counter1()) //24 var counter2=createCounter(countBy: 10) print(counter2()) //10 print(counter2()) //20 print(counter2()) //30 print(counter2()) //40 var counter3=counter2 print(counter3()) //50 print(counter3()) //60 print(counter3()) //70 counter2=createCounter(countBy:3) print(counter2()) //3 print(counter2()) //6 print(counter2()) //9 print(counter3()) //80 print(counter3()) //90 print(counter3()) //100
9行目で、変数counter1に「呼び出される度に変数countに8ずつ加算するクロージャー」への参照をセットしています。counter1にはクロージャーを代入しているのと同じ状態なので、counter1を実行することができます。(10,11,12行目)
14行目で二つ目のカウンターを作っています。変数counter2には、9行目でcounter1にセットされたクロージャーとは別の新しいクロージャーへの参照がセットされ、そのクロージャーは9行目で作られたcountとは別の新しいcountを増分させます。
counter1を実行する度にcounter1に対応したcountが増分されますが、それはcounter2にセットされたクロージャーがキャプチャしているcountには影響を与えません。
Closures Are Reference Types
「ある変数あるいは定数」に「関数あるいはクロージャー」を代入するということは、実際にはその「ある変数あるいは定数」を「関数あるいはクロージャー」の参照として設定することです。sample1-1で言えば、counter1が参照するクロージャーを選んだ、ということでcounter1にクロージャー本体が代入された訳ではありません。
それは、二つの異なる定数・あるいは二つの異なる変数に、クロージャーを代入すると、両方の定数・あるいは変数が同一のクロージャーを参照することを意味します(20行目以降)。
参考:
https://docs.swift.org/swift-book/LanguageGuide/Closures.html