web-dev-qa-db-ja.com

Swiftでは[未所有の自己]クロージャーを常に使用しなければならない

WWDC 2014セッション403 Intermediate Swift および transcript では、次のスライドがありました

enter image description here

その場合、スピーカーは、[unowned self]を使用しないとメモリリークが発生すると述べました。クロージャーの内側で常に[unowned self]を使うべきということですか?

Swift WeatherアプリのViewController.Swiftの64行目 、私は[unowned self]を使用しません。しかし、私は@IBOutletself.temperatureのようないくつかのself.loadingIndicatorを使ってUIを更新します。私が定義したすべての@IBOutletweakであるため、問題ないかもしれません。しかし安全のために、私たちは常に[unowned self]を使うべきですか?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}
429
Jake Lin

いいえ、[unowned self]を使用したくない場合は間違いありません。時には、クロージャが呼び出されるまでにクロージャがまだ存在していることを確認するために、クロージャに自分自身を捕獲させたいことがあります。

例:非同期ネットワーク要求を出す

非同期ネットワークリクエストを行う場合、doリクエストが終了したときにクロージャがselfを保持するようにします。そうでない場合、そのオブジェクトは割り当て解除されている可能性がありますが、リクエストの終了を処理できるようになります。

unowned selfまたはweak selfを使用する場合

本当に[unowned self]または[weak self]を使いたいのは、 強い参照サイクルを作成するときだけです 。強い参照サイクルは、オブジェクトがお互いを所有することになる(おそらく第三者を通じて)所有権のループがあるときです。

クロージャの特定のケースでは、その内部で参照されているすべての変数がクロージャによって「所有」されることを理解する必要があります。クロージャが周囲にある限り、それらのオブジェクトは周囲にあることが保証されています。その所有権を止める唯一の方法は、[unowned self]または[weak self]を実行することです。つまり、あるクラスがクロージャを所有していて、そのクロージャがそのクラスへの強い参照を取得した場合、クロージャとクラスの間に強い参照サイクルがあります。これは、クラスがクロージャを所有する何かを所有しているかどうかも含みます。

具体的にはビデオから

スライド上の例では、TempNotifieronChangeメンバー変数を介してクロージャーを所有しています。彼らがselfunownedとして宣言しなかった場合、クロージャーはselfも所有し、強力な参照サイクルを作成します。

unownedweakの違い

unownedweakの違いは、weakはOptionalとして宣言されていますが、unownedはそうではないということです。それをweakと宣言することによって、ある時点でクロージャの内側でそれがゼロであるかもしれないというケースに対処することができます。たまたまnilでないunowned変数にアクセスしようとすると、プログラム全体がクラッシュします。ですから、unownedを使うのは、クロージャーが動いている間は、変数が常に動いているということが正の場合です。

804
drewag

更新2016/11

私はこの答えを拡張することについての記事を書きました(ARCが何をするのか理解するためにSILを調べます)、それをチェックしてください ここ

元の答え

これまでの答えでは、どちらを使用するのか、またその理由については、単純明快な規則は示されていませんので、いくつか追加しておきます。

所有されていないか弱い議論は、変数の{lifetimeとそれを参照するクロージャの問題に帰着します。

Swift weak vs unowned

シナリオ

考えられるシナリオは2つあります。

  1. クロージャは変数の有効期間が同じであるため、クロージャは到達可能になります変数が到達可能になるまでのみ。変数とクロージャの寿命は同じです。この場合、参照を 未所有 として宣言する必要があります。一般的な例は、親のコンテキスト内で何かを実行し、他の場所で参照されていないことが親よりも長生きしない、小さなクロージャの多くの例で使用されている[unowned self]です。

  2. クロージャの有効期間は変数のものとは無関係です。クロージャは変数が到達できなくなっても参照される可能性があります。この場合、参照を weak として宣言し、それを使用する前にnilではないことを確認してください(強制的にアンラップしないでください)。これの一般的な例は、クロージャーのいくつかの例で見られる、完全に無関係な(一生のうちに)委任オブジェクトを見ることができる[weak delegate]です。

実際の使い方

それで、あなたはどれを実際に使うのでしょうか?

Twitterからの引用Joe Groff

所有していない方が速く、不変性と非選択性が可能です。

弱い人がいらないのなら、使ってはいけない。

Unowned*の内部動作についてもっと知ることができます ここ

*通常、所有されていない参照にアクセスする前に実行時チェック(無効な参照のクラッシュにつながる)を示すために、所有されていない(安全な)とも呼ばれます _

169

具体的にView Controllerの具体例をいくつか追加すると思いました。ここでのスタックオーバーフローに関する説明だけでなく、説明の多くは非常に優れていますが、実世界の例を使用した方がうまくいきます(@drewagはこれでうまくいってきました)。

  • あなたがネットワークからの応答を処理するためのクロージャを持っているならば、それらは長命であるので、weakを使います。 View Controllerはリクエストが完了する前に閉じることができるため、クロージャが呼び出されたときにselfは有効なオブジェクトを指していません。
  • ボタンのイベントを処理するクロージャがある場合これはunownedになる可能性があります。これは、View Controllerが消滅すると同時に、selfから参照している可能性のあるボタンやその他の項目が同時に消滅するためです。クロージャブロックも同時に消えます。

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // closure must be held in this class. 
    
          override func viewDidLoad() {
              // use unowned here
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // won't happen after vc closes. 
              }
              // use weak here
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // could be called any time after vc closes
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // rest of class below.
     }
    
69
possen

self がクロージャでゼロになる可能性がある場合は、 [weak self] を使用してください。

self がクロージャの中で決してnilにならない場合は、 [unowned self] を使用してください。

Apple Swiftのドキュメントには、クロージャで strong weak 、および 未所有 を使用することの違いを説明した画像の素晴らしいセクションがあります。

https://developer.Apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html

66
TenaciousJay

ここに Apple Developer Forums による素晴らしい詳細が記載されています。

unowned vs unowned(safe) vs unowned(unsafe)

unowned(safe)は、アクセス時にオブジェクトがまだ生きていることを表明する非所有参照です。アクセスされるたびに暗黙のうちにx!でラップ解除される、弱いオプションの参照のようなものです。 ARCのunowned(unsafe)__unsafe_unretainedに似ています。これは非所有参照ですが、オブジェクトがまだアクセス中であることを実行時にチェックするものではないため、参照がぶら下がるとゴミメモリに入ります。現在のunownedは常にunowned(safe)の同義語ですが、意図は、-Ofastビルドのunowned(unsafe)に最適化され、ランタイムチェックが無効になっていることです。

unownedweak

unownedは実際にはweakよりもずっと単純な実装を使用しています。ネイティブSwiftオブジェクトは2つの参照カウントを持ち、unowned参照はstrong参照カウント ではなく未所有参照カウント にぶつかります。strong参照カウント がゼロに達するとオブジェクトは非初期化されますが、未所有参照カウント もヒットするまで実際に割り当て解除されません。ゼロ。所有されていない参照があると、メモリが少し長く保持されますが、unownedが使用されている場合は、通常は問題になりません。関連オブジェクトはいずれにしても寿命がほぼ等しいはずです。弱い参照をゼロにするために使用されるサイドテーブルベースの実装。

更新: 現代のSwiftでは weakは内部的にunownedと同じメカニズムを使用しています 。 Objective-CのweakとSwiftのunonwedを比較するため、この比較は正しくありません。

理由

参照を所有して0になった後もメモリを存続させる目的は何ですか?コードが初期化解除された後に所有されていない参照を使用してオブジェクトに対して何かをやろうとするとどうなりますか?

保持カウントがまだ利用できるように、メモリは生き続けています。このようにして、誰かが所有されていないオブジェクトへの強い参照を保持しようとすると、ランタイムはそのオブジェクトを保持しても安全であることを確認するために強い参照カウントがゼロより大きいことを確認できます。

オブジェクトによって保持されている所有者または所有されていない参照はどうなりますか?初期化解除されるとオブジェクトから切り離されるか、または最後の所有されていない参照が解放されてからオブジェクトの割り当てが解除されるまで保持されますか?

そのオブジェクトが所有するすべてのリソースは、そのオブジェクトの最後の強力な参照が解放されるとすぐに解放され、そのdeinitが実行されます。所有されていない参照はメモリを存続させるだけです - 参照カウントを持つヘッダを除いて、その内容はがらくたです。

興奮した、ハァッか。

48

ここにいくつかの素晴らしい答えがあります。しかし、Swiftが弱参照を実装する方法に対する最近の変更は、みんなの弱い自己対自分の知らない自己使用法の決定を変えるはずです。所有されていない自己にアクセスする方が弱い自己にアクセスするよりもはるかに速いため、以前は、所有されていない自己を使用して最高のパフォーマンスを得た場合、弱い自己より優れていました。

しかしMike Ashは、Swiftがどのようにサイドテーブルを使用するように弱いvarsの実装を更新し、これによって弱い自己パフォーマンスが大幅に改善されるかを文書化しています。

https://mikeash.com/pyblog/friday-qa-2017-09-22-Swift-4-weak-references.html

弱い自己に大きなパフォーマンス上のペナルティがあるわけではないので、今後はそれを使用しないことをお勧めします。 weak selfの利点は、それがオプションなので、より正確なコードを書くのがはるかに簡単になることです。基本的に、Swiftがこのような優れた言語である理由です。あなたはあなたがどのような状況が所有されていないselfの使用にとって安全であるかを知っていると思うかもしれませんが、他の多くの開発者コードをレビューした私の経験は、ほとんどありません。所有されていないselfの割り当てが解除されていた多くのクラッシュを修正しました。通常は、コントローラの割り当てが解除された後にバックグラウンドスレッドが完了する状況で発生します。

バグやクラッシュはプログラミングの中で最も時間がかかり、苦痛で高価な部分です。正しいコードを書き、それらを避けるように最善を尽くしてください。私は、オプショナルをアンラップせず、弱い自己ではなく所有していない自己を決して使わないことをルールにすることをお勧めします。あなたは時代を逃す何かを失うことはありません開封を強制し、所有されていない自己は実際に安全です。しかし、クラッシュやバグを見つけてデバッグするのが困難なことから、多くのことが得られます。

33

によるとApple-doc

  • 弱い参照は常にオプションの型であり、それらが参照するインスタンスの割り当てが解除されると自動的にnilになります。

  • 取り込まれた参照が決してnilにならないのであれば、弱い参照ではなく、常に所有されていない参照として取り込まれるべきです。

例 -

    // if my response can nil use  [weak self]
      resource.request().onComplete { [weak self] response in
      guard let strongSelf = self else {
        return
      }
      let model = strongSelf.updateModel(response)
      strongSelf.updateUI(model)
     }

    // Only use [unowned self] unowned if guarantees that response never nil  
      resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
     }
3
Jack

上記のいずれにも意味がない場合:

tl; dr

implicitly unwrapped optionalのように、guaranteeを使用できる場合、参照が使用時にnilにならない場合は、unownedを使用します。そうでない場合は、weakを使用する必要があります。

説明:

以下で次を取得しました: weak unowned link 。私が集めたものから、所有されていない自己ca n'tはゼロであるが、弱い自己はありえ、所有されていない自己はぶら下がりポインタにつながる可能性があります... Objective-Cで悪名高い。それが役に立てば幸い

「UNOWNED弱参照と非所有参照は同様に動作しますが、同じではありません。」

所有されていない参照、like弱参照、参照されているオブジェクトの保持カウントは増加しません。ただし、Swiftでは、所有されていない参照には、オプションではないという追加のbenefitがあります。これにより、オプションのバインディングを使用するのではなく、管理が容易になります。これはImplicitly Unwrapped Optionalsとは異なりません。さらに、非所有参照はnon-zeroingです。 これは、オブジェクトの割り当てが解除されたときに、ポインターがゼロにならないことを意味します。これは、所有されていない参照を使用すると、場合によっては、leadダングリングポインターへ。私のようにObjective-Cの時代を覚えているオタクにとって、所有されていない参照はunsafe_unretained参照にマップされます。

これは、少し混乱するところです。

弱い参照と所有されていない参照はどちらも保持カウントを増やしません。

どちらも保持サイクルを中断するために使用できます。それで、いつそれらを使うのでしょうか?!

Appleのドキュメントによると:

validである場合は常に、弱い参照を使用して、その参照が存続期間中のある時点でゼロになるようにします。逆に、参照が初期化中に一度設定されると決して参照がゼロにならないことがわかっている場合は、所有されていない参照を使用します。

0
Dennis

クロージャの強力な参照サイクル

クラスインスタンスのプロパティにクロージャを割り当て、そのクロージャの本体でインスタンスを取得した場合も、強い参照サイクルが発生する可能性があります。このキャプチャは、クロージャの本体がself.somePropertyなどのインスタンスのプロパティにアクセスするか、またはクロージャがself.someMethod()などのインスタンスのメソッドを呼び出すために発生する可能性があります。いずれの場合も、これらのアクセスによってクロージャがselfを「捕獲」し、強力な参照サイクルが発生します。 クロージャー内の値を取り込む /

Swiftはクロージャキャプチャリストとして知られているこの問題に対する優雅な解決策を提供します。キャプチャリストは、クロージャの本文内で1つ以上の参照型をキャプチャするときに使用する規則を定義します。 2つのクラスインスタンス間の強い参照サイクルと同様に、キャプチャされた各参照は、weak参照ではなくunownedまたはstrong参照として宣言します。 weakまたはunownedの適切な選択は、コードのさまざまな部分間の関係によって異なります。 詳細はこちらSO

  • その参照がその存続期間中のある時点でweakになるのが有効であるときはいつでもnil参照を使用してください。
  • 参照が初期化時に設定された後でneverunownedになることがわかっている場合は、nil参照を使用します。

例題のドキュメント

0
yoAlex5