web-dev-qa-db-ja.com

Swift閉鎖により、自己との強い保持サイクルが発生します

私はこれを正しく理解しているかどうか知りたいだけです。したがって、Apple docsによると、クラスインスタンスのプロパティとしてクロージャを作成し、そのクロージャがself(クロージャプロパティを作成したクラス)を参照する場合)により、最終的に強力な保持サイクルが発生します。クラスもクロージャもリリースされます。つまり、一般的に言えば、プロパティを持つクラスがあり、そのプロパティがクロージャであり、クラス内でそのクロージャの機能を割り当てると、クロージャプロパティを宣言しますこれは強い保持サイクルを引き起こします私が意味することの簡単な例を示します

class SomeViewController{
  let myClosure:()->Void

  public func someFunction(){
    ....bunch of code
    myClosure = {
       self.dismiss(blahBlahBlah)
    }
  }
}

クロージャはクロージャプロパティを作成するクラスであるselfへの強い参照を保持するため、これは最終的に保持サイクルを引き起こします。これをApple=に従って修正するには、次のようにキャプチャリストを定義します。

class SomeViewController{
  let myClosure:()->Void

  public func someFunction(){
    ....bunch of code
    myClosure = { [weak self] in
       self?.dismiss(blahBlahBlah)
    }
  }
}

Inステートメントの前に[weak self]を置いていることに注目してください。これにより、クロージャーは、自己への弱い参照のみを保持し、強い参照を保持しないことを認識します。 IMは、自己がクロージャーを存続できる場合は弱い、またはクロージャーと自己が同じ期間存続する場合は所有されていない場合に使用することになっています。

私はここからこの情報を取得しました 自動参照カウント とそのリンクのクロージャーの強い参照サイクルセクションに、この文があります "強い参照サイクルも発生する可能性がありますクラスインスタンスのプロパティにクロージャを割り当て、そのクロージャの本体がインスタンスをキャプチャします。 "Im約90%確かにこれを正しく理解していますが、疑いの10%しかありません。これは正しいですか?

私がこれを尋ねる理由は、私のビューの一部のボタンにコールバックを使用しているためです。そして、それらのコールバックはselfを呼び出しますが、そのシナリオのselfは、実際のビュー自体ではなく、コールバックに応答しているビューコントローラーです。私が強調したその文から私が疑っているのはここです。すべてのボタンコールバックに[weak self]を付ける必要はないと思いますが、確認しているだけです。これの例はこちらです

class SomeViewController {
    let someSubview:UIView

    override viewDidLoad() {
       //Some Subview has a button and in that view I just have some action that gets fired off calling the callback here in the view controller I don't need to use the [weak self] in this scenario because closure property is not in this class correct?
       someSubview.someButtonsCallback = {
       ....run code then 
       self?.dismiss(blahBlahBlah)
     }
 }
11
Esko918

はい、それでも保持サイクルが発生する可能性があります。

最も単純な保持サイクルは、それぞれが相互に強い参照を持つ2つのオブジェクトですが、3ウェイ以上の保持サイクルも可能です。

あなたの場合、ビューを含むビューコントローラーにボタン(強い参照)があります。ボタンにはクロージャーへの強い参照があります。クロージャは、selfを使用してビューコントローラを強く参照します。したがって、ビューはボタンを所有しています。ボタンはクロージャーを所有しています。クロージャはビューコントローラを所有します。ビューコントローラーを閉じた場合(モーダルだったなど)、割り当てを解除する必要があります(SHOULD)。ただし、この3ウェイ保持サイクルがあるため、割り当てが解除されることはありません。 printステートメントを使用してdeinitメソッドをビューコントローラーに追加し、それを試してください。

解決策は、最初の例と同じようにキャプチャリスト([weak self]ビット)を追加することです。

一般的なパターンは、キャプチャリストを追加して、クロージャー内の弱い変数を強い変数にマップすることです。

let myClosure = { [weak self] in 
  guard let strongSelf = self else { return }
  //...
  strongSelf.doSomething()
}

このようにして、クロージャはまだアクティブであるが、クロージャを所有するオブジェクトが解放された場合、最初のガードステートメントはselfがnilであることを検出し、クロージャの最初で終了します。それ以外の場合は、オプションを参照するたびにオプションをアンラップする必要があります。

場合によっては、キャプチャリスト内のオブジェクト(これらの例では自分自身)が実行中のクロージャの途中で割り当て解除される可能性があり、これにより予測できない動作が発生する可能性があります。 (ゴリーの詳細:これは、キャプチャリスト内のオブジェクトの所有者とは別のスレッドでクロージャが実行されている場合のみですが、完了ハンドラは通常バックグラウンドスレッドで実行されるため、発生します)

これを想像してみてください:

let myClosure = { [weak self] in 

  self?.step1() //1

  //time-consuming code

  self?.property = newValue //2

  //more time-consuming code

  self?.doSomething() //3

  //even more time-consuming code

  self?.doSomethingElse() //4
}

上記のコードでは、クロージャーがバックグラウンドスレッドで実行されている場合、ステップ1でselfがまだ有効である可能性がありますが、ステップ2を実行するまでに、selfは割り当て解除されています。同じことがステップ3と4にも当てはまります。クロージャの先頭にguard strongSelf = self else { return }を追加して、クロージャのエントリでテストし、自分がまだ有効であることを確認します。有効な場合は、クロージャにクロージャの実行に必要な期間のみ存続し、クロージャコードの実行中にselfが割り当て解除されないようにする強力な参照。)

9
Duncan C