##クロージャとは
クロージャとはスコープ内の変数や定数を保持したひとまとまりの処理のことである。これは再利用可能なひとまとまりの処理であり、クロージャは関数の1種であるため、関数との共通の点が多く見受けられます。関数を定義する際にはfuncが必要ですが、クロージャにはクロージャ式という別の定義方法があります。クロージャ式は、名前が不要であったり、型推論によって型の記述が省略可能な点など、関数よりも軽量化して定義できます。
##クロージャ式の定義
####基本文法
//雛形
{(引数名1: 型, 引数名2: 型...) -> 戻り値の型 in
クロージャの実行時に実行される文
必要に応じてreturn文で戻り値を返却する
}
//サンプルコード1(処理内容1文)
let double = { (x: Int) -> Int in
x * 2
}
double(2)
//実行結果
4
//サンプルコード2(処理内容2文以上)
var plus = { (x: Int) -> Int in
var y = 0
y += x
return x + y
}
plus(3)
//実行結果
6
3
上記の雛形がクロージャ式の基本になります。引数名は複数設定でき、引数と戻り値の型指定を基本的には行わなくてありません。
次にサンプルコード1を見ると返り値型は設定されているのに、処理内容がreturn文になっていません。これは**暗黙的なreturn(Implicit Returns)**と呼ばれるもので、クロージャ内の文が一つしかないと、returnを省略することができます。そのためサンプルコード2ではクロージャ内の文末がreturn文で記述されており、returnを記述しなかった場合コンパイルエラーになります。
####引数としての利用
let closure: (Int) -> Int
func Add(x: (Int) -> Int) {}
/////////////////////////////
(Int) -> (Int)
これがクロージャ型
最初のIntがxの型を表しており、2つ目のIntが戻り値の型を表している。
また上記の通りクロージャ型は通常の型と同じように扱えるため、変数・定数の方、関数の引数の型としても利用できます。
####型推論
クロージャの引数・戻り値の型は代入先の型から推論することで省略できることがあります。
//変数を宣言し、型をクロージャ型に指定
var closure: (String) -> Int
//クロージャ型で宣言した変数でクロージャ文を作成
closure = {string in
return string.count
}
closure("hello")
//実行結果
5
上記のように事前にクロージャ型の変数などを宣言していた場合、その変数を用いることで、引数と返り値の型を指定する必要がなくなります。結果として引数名を指定するだけでの実装ができます。
####クロージャの実行
先ほどのサンプルコードですでに実行していますが、クロージャの実行は関数の実行と同じで以下のような記述になります。
add(5)
クロージャが代入された変数名や定数名を記述し、その文末に()をつけ、のカッコ内に本引数を指定すれば実行できます。
####引数における注意点
クロージャ式の引数は関数の引数に非常に似ていますが、数点異なる点があるので下記の表に記載します。
○が利用可能、×が利用不可です。
利用可能な機能 | 関数 | クロージャ式 |
---|---|---|
外部引数名 | ○ | × |
内部引数 | ○ | ○ |
デフォルト引数 | ○ | × |
インアウト引数 | ○ | ○ |
可変長引数 | ○ | ○ |
簡略引数名 | × | ○ |
外部引数・内部引数とは |
//x・yが内部引数(ローカル引数)、width・heightが外部引数
func areaOfSquare(width x: Int, height y: Int) {
print(x * y)
}
areaOfSquare(width: 10, height: 20)
上記のコードを見ると、関数内で一つの引数に対して2つの名前が設定されています。
この2つの引数名のうちの左側(width,height)が外部引数、x・yが内部引数です。
外部引数は関数を実行するときに、内部引数とは異なる名前で本引数を設定したい場合に用います。そのためxとyどっちがwidthでheightなのかをわかるようにするために、xとyの左よこに目印として記述されています。
内部引数は言葉の通りメソッド内の処理内容でのみ使う引数名のことをさします。
デフォルト引数
func areaOfSquare(x: Int = 20, height y: Int = 50) {
print(x * y)
}
areaOfSquare(x: 10)
デフォルト引数とは上記のコード通り仮引数に初期値を設定しておくことで、メソッド実行時に引数を指定しなくても初期値を参照して実行可能にする引数です。
簡略引数名
let isEqual: (Int, Int) -> Bool = {
return $0 == $1
}
isEqual(1,1) //true
簡略引数名とは上記のコードのように、クロージャ定義内で引数名ではなく、$(インデックス値)で指定する引数のことです。そのためこの例のように引数が2つある場合は、一つ目のIntが$0で、二つ目のIntが$1になります。
let isEqual = {
return $0 == $1
}
///コンパイルエラー
注意点としては上記のように定義内で引数の型指定をしないため、このようにisEqualに対して引数を指定しない型推論ができないためコンパイルエラーになります。
####戻り値
//戻り値がないクロージャ
let emptyReturnValueClosure: () -> Void = {}
戻り値がない関数を定義できるのと同じで、クロージャにも戻り値がない状態で定義できます。Swiftでは()とVoidの意味合いは同じですが、通常クロージャを定義する際には引数が存在しない場合は()を、返り値が存在しない場合はVoidを用いるので注意してください。
####変数と定数のキャプチャ
通常ローカルスコープで定義された変数や定数は、ローカルスコープ内ではしか使用できません。しかし、クロージャでは参照している変数や定数は、クロージャのスコープ外であっても、クロージャの実行時のみ使用できます。これはクロージャ自身が定義されたスコープの変数・定数を保持しているためです。この昨日をキャプチャと言います。
let goodbye: (String) -> String
do {
let symbol = "!"
greeting = { user in
return "Goodbye, \(user)\(symbol)"
}
}
greeting("Kamizyou" // Goodbye, Kamizyou!
symbol // symbol単体だとスコープ外のためコンパイルエラーを起こす。
上記のように!はクロージャのローカルスコープ内の定数であるにも関わらず、greeting()実行時にしっかり出力されています。これはクロージャがキャプチャによって、自分自身が定義されたスコープの定数や変数を保持しているためです。その証拠にsymbolだけを実行したらコンパイルエラーになります。ここで注意しておきたいのが、あくまでクロージャは定数や変数をキャプチャしているのであって、定数や変数の値をキャプチャしているのではありません。その証明として下記をご覧ください。
let counter: () -> Int
do {
var count = 0
counter = {
count += 1
return count
}
}
counter() //1
counter() //2
上記のコードを見ると、1回目のcounter()と2回目のcounter()で出力結果が変わっています。
これはクロージャがキャプチャしているのが変数や定数という箱であるため、中の数値は保持されず実行するたびに更新されるためです。
そのためこのプログラムの場合、実行するたびに値が増えることになります。
##最後に
今回はもう疲れたので、ここまでにしておきます。
次回以降もクロージャのことについて投稿するので、もし気が向いたらご覧ください!!!