よくクロージャが理解できない、クロージャの使い所が解らない、というのを目にします。
以下に私のクロージャに対する理解を書いてみます。あくまで私の理解です。
引数としてのクロージャ
私の引数としてのクロージャの理解としては、呼び出し側で処理が記述できる、です。
呼び出され側はクロージャに引数として値を渡し、
「値は渡すから、その値はそちらで好きに料理してね」という具合です。
よく使う解りやすいものとしては「コールバック関数」があるかと思います。
私としては引数の型(以下コードでは (Int) -> Void)はデリゲートのプロトコルのようなものだと認識しています。
こうする事で (Int) -> Void)の制約の素で、呼び出し側でそれぞれ自由に処理ができるようになります。
以下のコードでは受け取った引数の値を倍と半分にする処理を記述しています。
class Sam{
let val:Int = 10
func aa(cl: (Int) -> Void){
cl(self.val)
}
}
class Sam2{
let sam = Sam()
//引数の値を倍にする処理
func bai(){
sam.aa{ arg in
print(arg * 2)
}
}
//引数の値を半分にする処理
func hanbun(){
sam.aa{ arg in
print(arg / 2)
}
}
}
let sam2 = Sam2()
sam2.bai() //20
sam2.hanbun() //5
このよに呼び出し側で値を倍にしたり、半分にしたりと処理を決める事ができます。
また、以下のようにifやswichのような条件分岐で処理を振り分けても良いかと思います。
class Sam{
let val:Int = 10
func aa(cl: (Int) -> Void){
cl(self.val)
}
}
class Sam2{
let sam = Sam()
func aa(){
sam.aa{ arg in
if(arg > 20){
print(arg * 2)
}else{
print(arg / 2)
}
}
}
}
var sam2 = Sam2()
sam2.aa() //5
因みに、クロージャは違うスコープ(やスレッド)で使う場合や保存したりする場合はescaping属性を指定する必要があります。
クロージャによる変数と定数のキャプチャ
ローカルスコープで定義された変数や定数は、ローカルスコープ内でしか使用できませんが、クロージャが参照している変数や定数は、クロージャが実行されるスコープが変数や定数が定義されたローカルスコープ以外であっても、クロージャの実行時に利用できます。これは、クロージャが自身が定義されたスコープの変数や定数への参照を保持しているためで、この機能をキャプチャと言います。
以下でクロージャがキャプチャしている様子を、クロージャでない普通の関数と比較してみます。
class Sam{
func aa1(){
var a = 1
a += 1
print(a)
}
func aa2(){
aa1()
aa1()
}
func aa3() -> () -> Void{
var a = 10
let cl = {
a += 1
print(a)
}
return cl
}
func aa4(){
let a = self.aa3()
a()
a()
}
}
let sam = Sam()
sam.aa2()
sam.aa4()
//表示結果
//2
//2
//11
//12
aa2関数
aa2関数は普通の関数aa1を2回実行しています。
普通関数は実行後はリセットされるので、1回目の実行で「2」と表示されても、実行後ローカル変数aの値は「1」に戻ります。
なので2回目の実行でも「2」と表示されます。
aa4関数
aa4関数はaa3関数から返されたクロージャを2回実行しています。
クロージャがaa3関数のローカル変数aを参照しているので1回目の実行で「11」、2回目の実行では1回での参照を保持しているので「12」となります。