#この投稿は何?
Swiftプログラミングにおけるキャプチャ・リストについて、公式ドキュメントの内容を独自に翻訳したものです。
###環境
macOS 10.15.7
Xcode 12.1
Swift 5.3
#キャプチャ・リストとは
既定の動作として、クロージャ式は「その周囲のスコープにある定数および変数」を強い参照によってキャプチャします。キャプチャリストを使用すると、クロージャで値をキャプチャする方法を明示的に制御することができます。
キャプチャ・リストは、パラメータのリストの前に、「角括弧で囲まれたカンマで区切ったリスト」として記述されます。キャプチャ・リストを使用する場合、パラメータ名、パラメータおよび戻り値の型を省略していても、in
キーワードを常に使用します。
{ [value1, value2, ...] in STATEMENT }
キャプチャ・リストのエントリは、クロージャ作成時に初期化されます。キャプチャ・リストの各エントリとなる定数は「周囲のスコープで同じ名前を持つ定数または変数」の値に初期化されます。
例えば、以下のコードでは、a
はキャプチャリストに含まれていますが、b
は含まれていません。
var a = 0
var b = 0
let closure = { [a] in // aは0で初期化される
print(a, b) // スコープ外でaを変更しても、影響しない
}
a = 10 // この変更は、キャプチャ・リストのaに影響しない
b = 10
closure()
// Prints "0 10"
スコープ内のa
は、クロージャが作成されたときに「スコープ外のa
の値」で初期化されますが、それらの値は特に繋がっているわけではありません。つまり、スコープ外のa
を変更しても、スコープ内のa
には影響しませんし、クロージャ内のa
への変更がクロージャ外のa
に影響することもありません。対照的に、スコープ外にはb
という名前の変数が1つしかないので、その変更はクロージャの内側と外側のどちらにも影響します。
ただし、キャプチャされた変数が「参照型のデータ」だった場合、この区別はありません。
例えば、以下のコードでは、「スコープ外にある変数」と「スコープ内にある定数」に、の2つのx
がありますが、どちらも参照型データなので同じオブジェクトを参照しています。
class SimpleClass {
var value: Int = 0
}
var x = SimpleClass() // ひとつめの変数x(x.valueは0で初期化される)
var y = SimpleClass()
let closure = { [x] in // ふたつめの定数x(x.valueは0で初期化される)
print(x.value, y.value) // スコープ外でxを変更すると、影響する
}
x.value = 10 // スコープ外での変更が、キャプチャ・リストのxにも影響する
y.value = 10
closure()
// Prints "10 10"
式の値の型がクラスの場合、式の値への「弱い参照」をキャプチャするために、キャプチャ・リストで式をweak
でマークできます。また、「所有されていない参照」をキャプチャするためには、キャプチャ・リストで式をunowned
でマークできます。
myFunction { print(self.title) } // 「強い参照」による値のキャプチャ(暗黙的)
myFunction { [self] in print(self.title) } // 「強い参照」による値のキャプチャ(明示的)
myFunction { [weak self] in print(self!.title) } // 「弱い参照」による値のキャプチャ
myFunction { [unowned self] in print(self.title) } // 「所有しない参照」で値をキャプチャ
また、キャプチャ・リスト内の値に名前を付けて、任意の式をバインドすることもできます。「バインドした式」はクロージャが作成されたときに評価され、値は指定された強度でキャプチャされます。
例えば、以下のようになります。
// "parent"として、弱い参照で"self.parent"をキャプチャする
myFunction { [weak parent = self.parent] in
print(parent!.title)
}
クロージャ式の詳細と例については、クロージャ式を参照してください。
キャプチャリストの詳細と例については、クロージャの強い参照サイクルの解決を参照してください。