クロージャの定義の一部としてキャプチャリストを定義して、クロージャとクラスインスタンス間の強い参照の循環を解決します。キャプチャリストには、クロージャの本体内で 1 つ以上の参照型をキャプチャするときに利用するルールを定義します。2 つのクラスインスタンス間での強い参照の循環と同じように、キャプチャされる参照を、強い参照よりも弱い参照、あるいは非所有の参照として宣言します。弱い参照と非所有の参照のどちらの選択が適切かは、コード間の関係によります。

NOTE
Swift では、self のメンバーを参照するときには(someProperty や someMethod() ではなく)self.someProperty や self.someMethod() と記述する必要があります。これにより、意図せず self をキャプチャしてしまう可能性があることを思い出すことができます。

キャプチャリストを定義

キャプチャリストの各項目は、(self のような)クラスインスタンスや、(delegate = self.delegate! のように)ある値で初期化される変数に対する参照の weak や unowned キーワードのペアです。これらのペアは角括弧内にカンマ区切りで記述されます。

クロージャのパラメータリストと戻り値の型の前にキャプチャリストを置きます。

lazy var someClosure: (Int, String) -> String = {
    [unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
    // クロージャ本体
}

文脈から推論できるためにクロージャがパラメータリストや戻り値の型を指定していない場合には、クロージャの一番初めにキャプチャリストを置いて in キーワードを続けます。

lazy var someClosure: () -> String = {
    [unowned self, weak delegate = self.delegate!] in
    // クロージャ本体
}

弱い参照と非所有の参照

キャプチャするクロージャとインスタンスが常に互いを参照し、常に同時に割り当て解除されるような場合には、非所有の参照としてクロージャ内にキャプチャを定義します。

一方で、キャプチャされた参照がその後 nil になるような場合には、弱い参照としてキャプチャを定義します。弱い参照は常にオプショナル型で、参照するインスタンスが割り当て解除されたとき自動的に nil になります。これにより、クロージャの本体内で存在を確認することができます。

NOTE
キャプチャされた参照が決して nil になることが無い場合には、弱い参照ではなく、常に非所有の参照としてキャプチャされるべきです。

非所有の参照は、前に見た HTMLElement の例での強い参照の循環を解決するために使用する適切なキャプチャ方式です。次は、循環を避ける HTMLElement クラスをどのように記述するかについてです。

class HTMLElement {

    let name: String
    let text: String?

    lazy var asHTML: () -> String = {
        [unowned self] in
        if let text = self.text {
            return "<\(self.name)>\(text)</\(self.name)>"
        } else {
            return "<\(self.name) />"
        }
    }

    init(name: String, text: String? = nil) {
        self.name = name
        self.text = text
    }

    deinit {
        print("\(name) is being deinitialized")
    }

}

HTMLElement のこの実装は、asHTML クロージャ内のキャプチャリストを追加していることを別にすれば、以前の実装とまったく同じです。このケースでは、キャプチャリストは [unowned self]で、「強い参照ではなく、非所有の参照として self をキャプチャする」ことを意味します。

前と同じように、HTMLElement インスタンスを生成して出力します。

var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// "<p>hello, world</p>" と出力

次は、キャプチャリストによる参照がどのようになっているかを示しています。

image: closureReferenceCycle02_2x

今回は、クロージャによる self のキャプチャは非所有の参照で、キャプチャしている HTMLElement インスタンスを強く保持し続けません。paragraph 変数からの強い参照に nil を設定した場合、次の例でデイニシャライザのメッセージ出力が確認できるように、HTMLElement インスタンスは割り当て解除されます。

paragraph = nil
// "p is being deinitialized" と出力

キャプチャリストについての詳細な情報は、Capture Lists を確認してください。


Portions of this page are translations based on work created and shared by Apple and used according to terms described in the Creative Commons Attribution 4.0 International License.