Swiftのクロージャーについてですが、クロージャを細かく説明するわけではありません。 Swiftのクロージャーの公式ドキュメントを見ると単純に和訳するだけでは理解するのが難しい用語があります(個人的にかもしれませんが・・・)
今回はドキュメントを読む前に必要な前提知識を解説しようと思います
Capture
まずは、キャプチャー
についてです
キャプチャとはスコープ外の変数などをクロージャ内から操作、参照できる仕様のことです
通常、関数内で定義された変数はその関数外からは参照することができません。 これはその変数のスコープが関数内に制限されており、関数の中で使用済みで不要になった変数は自動で破棄されるからです
func sample() -> Int{ var localCount = 0 return localCount } // 「Cannot find 'localCount' in scope」というコンパイルエラー print(localCount)
しかしクロージャでは関数内に定義した変数を外部から参照・操作できます。 例えば次のような例です
func sample() -> () -> Int { var localCount = 0 let closure = { () -> Int in localCount = localCount + 1 return localCount } return closure } var sample = sample() // 実行のたびにlocalCountが+1されていく sample() sample() sample()
sample関数の返り値はクロージャにしてあります。
これにより中に定義したクロージャをそのまま外部へ持ち出すことができます。
つまり変数sample
に格納されているのは{ () -> Int in localCount = localCount + 1 return localCount }
ということになります
クロージャの中ではlocalCountは未定義のはずですが、問題なく実行することができ、さらにクロージャの実行のたびにインクリメントされていきます。 つまりlocalCountは自動で破棄されることなく、メモリに残ったままです
Capture List
キャプチャと関連してキャプチャリスト
も和訳だけだと理解できないかもしれません
クロージャでキャプチャした変数や定義はメモリと強い参照(強参照)で紐付けられます。 そのため循環参照を引き起こし、メモリが解放されなくなる恐れがあります
循環参照を防ぐために、キャプチャリストを使用して明示的に変数の参照を制御することができます
記述方法は[変数名]形式で引数の前に記述します。 クロージャの省略記法を使用している場合でもキャプチャリストを記述する場合はinが必要になります
参照の強さはweak
やunowned
キーワードを使用して指定します
// 暗黙的な強参照 { () -> Void in print("TEST") } // 暗黙的な強参照 { print("TEST") } // 明示的な強参照 { [self] in print("TEST") } // 明示的な弱参照 { [weak self] in print("TEST") } // 明示的な非所有参照 { [unowned self] in print("TEST") }
Tuple
次にタプル
についてです
タプルとは順序付けされた値の集合体のことで、異なる型の値をひとつの変数、または定数で扱えます
配列や辞書はデータ型に非常に厳格で基本的に同じデータ型しか入らない一方、タプルを使用すればそのままのデータ型で保存できる、つまり各値を使用するときとかにいちいち型変換をしなくてもそのままの形で演算できます
ただし、値の追加、削除、また繰り返し処理などはできません
基本的には次のように定義して、値を取得できます
// タプルの宣言 let tuple = (id: 123, name: "ABC", isFlag: true) // 値の取得(ラベル指定) print(tuple.id) // 結果: 123 print(tuple.name) // 結果: ABC print(tuple.isFlag) // 結果: true // 値をインデックス番号から取得 print(tuple.0) // 結果: 123 print(tuple.1) // 結果: ABC print(tuple.2) // 結果: true
また、下記のように定義することもできます
// タプルの宣言 let (id, name, isFlag) = (123, "ABC", true) // 値の取得 print(id) // 結果: 123 print(name) // 結果: ABC print(isFlag) // 結果: true
値の更新は次のように行います。 また、タプルは参照渡しではなく、値渡しです
// タプルの宣言 var tuple = (id: 123, name: "ABC", isFlag: true) // 値の更新 tuple.id = 456 // 値の取得 print(tuple.id) // 結果: 456
in-out parameters
in-out parameters
についてです。
in-outパラメータとは参照渡しを実装できるパラメータです。
つまり、関数の中でその引数に変更を加えた場合、呼び出し側の元の変数にも変更が適用されます
次のようにinout
というキーワードを設定することで参照渡しを実装できるパラメータとなります
func main(type: inout Int) -> Int { return result }
注意点としては、参照を渡す場合は、&
を付与します。
そして、引数に直接データを渡すと、その引数は imutableになるのでコンパイルエラーになります
func main(type: inout Int) -> Int { return result } var type = 1 let result = main(type: &type) let result = main(type: 1) // NG
inoutパラメータを使うメリットとしては、参照を使用することでメモリコピーによるオーバーヘッドを避けることができます
しかし、inoutパラメータを作るのは、基本的には避けた方がよいでしょう。 意図しないところで値の書き換えが起こるというのが、バグの元になります。 関数から新しいデータを返してもらったほうが、プログラムのロジックを追うのが簡単になるので、良い気がします
最後に
個人的にここで挙げた内容を把握した上で英語ドキュメントを読むと理解しやすく、また理解が深まると思います!!