Swiftのクロージャー内では常に[unowned self]を使用する必要がありますか


467

WWDC 2014セッション403 Intermediate Swiftトランスクリプトで、次のスライドがありました

ここに画像の説明を入力してください

その場合スピーカーは言った、[unowned self]そこで使わないとメモリリークになる。常に[unowned self]クロージャー内で使用する必要があるという意味ですか?

スウィフト天気アプリのViewController.swiftのライン64、私は使用しません[unowned self]。しかし、および@IBOutletのようないくつかを使用してUIを更新します。私が定義したはすべてであるため、問題ない可能性があります。しかし、安全のために、常に使用する必要がありますか?self.temperatureself.loadingIndicator@IBOutletweak[unowned self]

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

画像のリンクが壊れている
ダニエルゴメスリコ

@ DanielG.R。ありがとう、見えます。i.stack.imgur.com/Jd9Co.png
ジェイクリン

2
私が誤解していない限り、スライドに示されている例は正しくありonChangeません。[weak self]これはパブリック(内部ではありますが)プロパティなので、クロージャーである必要があります。したがって、別のオブジェクトがクロージャーを取得して保存し、TempNotifierオブジェクトを保持します(無期限にusingオブジェクトは、)への独自の弱い参照を介しonChangeて、TempNotifierがなくなるまで、クロージャを解放しませんでしたTempNotifier。場合var onChange …したprivate var onChange …後、[unowned self]正しいだろう。 私はこれを100%確信しているわけではありません。私が間違っている場合、誰かが私を訂正してください。
Slipp D. Thompson

@Jake Lin `var onChange:(Int)-> Void = {}`中括弧は空のクロージャーを表しますか?空の配列を定義するのと同じ[]ですか?Appleのドキュメントに説明がありません。
bibcy 2016

@bibscyはい、{}空のクロージャー(クロージャーのインスタンス)がデフォルト(何もしない)で(Int) -> Void、クロージャーの定義です。
ジェイクリン

回答:


871

いいえ、使用したくない場合もあります[unowned self]。ときどき、クロージャが呼び出されたときまでにそれがまだ存在することを確認するために、クロージャが自分自身をキャプチャすることを望みます。

例:非同期ネットワーク要求を行う

あなたが作っている場合は、非同期ネットワーク要求を行うには、閉鎖を保持したいself場合は、要求が終了するため。そのオブジェクトは別の方法で割り当て解除された可能性がありますが、それでも要求の終了を処理できるようにしたいと考えています。

いつ使用するunowned selfweak self

本当に使用したいとき、[unowned self]または強い参照サイクルを[weak self]作成するときだけです。強い参照サイクルとは、オブジェクトが(おそらくサードパーティを介して)所有し合う所有権のループが存在する場合であり、したがって、オブジェクトがお互いに固執することを保証しているため、割り当てが解除されることはありません。

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

特にビデオの例では

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

unownedweak

違いunownedとはweakつまりweakオプションの中のように宣言されているunownedではありません。それを宣言するweakことで、ある時点でクロージャー内でnilになる可能性がある場合を処理できます。unownedたまたまnilである変数にアクセスしようとすると、プログラム全体がクラッシュします。したがってunowned、クロージャが周りにある間、変数は常に周りにあるということがポジティブである場合にのみ使用してください


1
こんにちは。すばらしい答えです。私は無所有の自分を理解するのに苦労しています。weakSelfを単に「自己がオプションになる」という理由で使用するのは、私には十分ではありません。なぜ私は特に「未所有の自己」を使用したいと思うstackoverflow.com/questions/32936264/...

19
@robdashnash、所有されていないselfを使用する利点は、オプションであるラップを解除する必要がないことです。これは、仕様上、nilになることはないとわかっている場合、不要なコードになる可能性があります。結局のところ、所有されていない自分は簡潔にするために、そしておそらくあなたがnil値を期待しないことを将来の開発者へのヒントとして使用されます。
-drewag

77
[weak self]非同期ネットワークリクエストで使用するケースは、そのリクエストを使用してビューにデータを入力するビューコントローラーです。ユーザーがバックアウトすると、ビューにデータを入力する必要がなくなり、ビューコントローラーへの参照も不要になります。
デビッドジェームズ

1
weak参照はnil、オブジェクトが割り当て解除されるときにも設定されます。unowned参照はそうではありません。
BergQuester 2017年

1
私は少し混乱しています。unowned以下のために使用されているnon-Optionalweakに使用されOptional、私たちがそうselfですOptionalnon-optional
ムハンマドナヤブ2018年

193

2016年11月更新

私はこの回答を拡張するこの記事(ARCの機能を理解するためにSILを調べます)を書いたので、こちらで確認してください

元の答え

上記の回答では、どちらをどのように使用するか、およびその理由を簡単に説明しているわけではないため、いくつか補足しておきます。

所有されていないか弱い議論は結局、変数の寿命とそれを参照するクロージャの問題に要約されます。

迅速な弱いvs非所有

シナリオ

次の2つのシナリオが考えられます。

  1. クロージャは変数のライフタイムが同じであるため、変数が到達可能になるまでクロージャに到達できます。変数とクロージャの寿命は同じです。この場合、参照をunownedとして宣言する必要があります。一般的な例は、[unowned self]親のコンテキストで何かを行う小さなクロージャーの多くの例で使用されており、他の場所で参照されていないため、親よりも長く存続しません。

  2. クロージャの有効期間は変数の1つから独立しています。クロージャは、変数に到達できなくなった場合でも参照できます。この場合、参照を弱いものとして宣言し、それが使用される前にnilでないことを確認する必要があります(アンラップを強制しないでください)。これの一般的な例は[weak delegate]、完全に関連のない(存続期間に関して)デリゲートオブジェクトを参照するクロージャのいくつかの例で見ることができます。

実際の使用

では、実際にほとんどの場合、どちらを使用しますか?

ツイッターからジョー・グロフを引用

Unownedはより高速で、不変性と非選択性を可能にします。

ウィークが必要ない場合は、使用しないでください。

所有していない*内部の仕組みについて詳しくは、こちらをご覧ください

* 通常はunowned(safe)とも呼ばれ、所有されていない参照にアクセスする前にランタイムチェック(無効な参照のクラッシュにつながる)が実行されることを示します。


26
オウムの説明「自分がnilになる可能性がある場合は週を使用し、nilになることができない場合はunownedを使用する」を聞くのはうんざりです。わかった、100万回聞いた!この答えは、OPの質問に直接答えるプレーンな英語で自己がnilになる可能性がある場合について、実際に深く掘り下げています。この素晴らしい説明をありがとう!
TruMan1 2016年

@ TruMan1に感謝します。私は実際にこれについてブログに投稿する投稿を書いています。回答がリンクで更新されます。
ウンベルトライモンディ2016年

1
いい答え、とても実用的。私は、パフォーマンスに敏感な弱い変数のいくつかを非所有に切り替えるようになりました。
original_username

「クロージャの寿命は変数の寿命から独立しています」ここにタイプミスはありますか?
ハニー

1
クロージャーが常に親オブジェクトと同じ存続期間を持っている場合、オブジェクトが破棄されるときに参照カウントがとにかく処理されませんか?この状況で、所有されていないか弱い人に悩まされるのではなく、単に「自己」を使用できないのですか?
LegendLength 2017

105

ビューコントローラーの具体的な例をいくつか追加すると思います。ここでのスタックオーバーフローだけでなく、説明の多くは本当に良いですが、私は実際の例を使用してよりよく作業します(@drewagはこれから良いスタートを切りました):

  • ネットワーク要求からの応答を処理するクロージャーがある場合は、weakそれらが長命なので、を使用します。ビューコントローラーはリクエストが完了する前に閉じる可能性があるためself、クロージャーが呼び出されたときに有効なオブジェクトを指さなくなります。
  • ボタンのイベントを処理するクロージャーがある場合。これはunowned、ビューコントローラがなくなるとすぐに、ボタンとそれが参照している可能性のある他のアイテム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.
     }

17
これにはもっと賛成票が必要です。ボタンプレスクロージャがビューコントローラの有効期間外に存在しないため、所有されていないものを使用できることを示す2つの確かな例ですが、UIを更新するほとんどのネットワーク呼び出しは弱い必要があります。
Tim Fuqua

2
明確にするために、クロージャーブロックでselfを呼び出すときは、常にunownedまたはweakを使用しますか?それとも、私たちが弱い/所有されていないと呼ばない時がありますか?もしそうなら、あなたもその例を提供できますか?
luke

どうもありがとうございます。
Shawn Baek

1
これにより、[弱い自己]と[非所有の自己]についての理解が深まりました。@ possenに感謝します。
トミー

これは素晴らしい。ユーザーインタラクションに基づくアニメーションがあり、終了するまでに時間がかかる場合はどうなりますか?次に、ユーザーは別のviewControllerに移動します。その場合、私はまだ正しいのweakではなく使用しているはずunownedですか?
ハニー

67

クロージャでselfがnilである可能性がある場合は、[weak self]を使用します。

クロージャーで自分自身がnilにならない場合は、[unowned self]を使用します。

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

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


50

ここに、Apple Developer Forumsからのすばらしい引用があり、美味しい詳細を説明しています。

unownedvs unowned(safe)vsunowned(unsafe)

unowned(safe)オブジェクトがまだ生きていることをアクセス時にアサートする非所有参照です。これは、x!アクセスされるたびに暗黙的にアンラップされる、弱いオプションの参照のようなものです。 ARCにunowned(unsafe)__unsafe_unretainedています。これは非所有参照ですが、オブジェクトがアクセス時にまだ生きているかどうかの実行時チェックは行われないため、ぶら下がっている参照はガベージメモリに到達します。 unownedは常にunowned(safe)現在の同義語ですが、その目的は 、ランタイムチェックが無効になっている場合unowned(unsafe)-Ofastビルドで最適化されることです。

unownedweak

unowned実際には、よりもはるかに単純な実装を使用していweakます。ネイティブSwiftオブジェクトには2つの参照カウントがあり、unowned 参照は強い 参照カウントではなく、所有されていない参照カウント押し上げます強い参照カウントがゼロに達すると、オブジェクトは初期化解除されますが、所有されていない参照カウントもゼロに達するまで、実際には割り当て解除されません 。これにより、所有されていない参照がある場合、メモリが少し長く保持されますが、通常、これは問題ではありません。unowned とにかく、関連オブジェクトのライフタイムはほぼ等しい必要があり、弱参照のゼロ化に使用されるサイドテーブルベースの実装よりもはるかにシンプルでオーバーヘッドが少ないためです。

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

理由

所有する参照が0に達した後、メモリを維持する目的は何ですか?コードが非初期化された後に、所有されていない参照を使用してオブジェクトに対して何かを行おうとするとどうなりますか?

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

オブジェクトが保持する所有または非所有の参照はどうなりますか?オブジェクトの存続期間は、オブジェクトが初期化解除されたときにオブジェクトから切り離されていますか、それとも、所有されていない最後の参照が解放された後にオブジェクトが割り当て解除されるまで、メモリも保持されますか?

オブジェクトの最後の強い参照が解放され、オブジェクトのdeinitが実行されるとすぐに、オブジェクトが所有するすべてのリソースが解放されます。所有されていない参照は、メモリを維持するだけです。参照カウントのあるヘッダーは別として、その内容はジャンクです。

興奮しましたね?


38

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

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

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

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

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


最後の段落の更新とアーメンをありがとう。
モットー

1
では、新しい変更後、のweak代わりに使用できないことがありunownedますか?
ハニー

4

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)
     }

0

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

tl; dr

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

説明:

私は次の場所で次の情報を取得しました:所有者のいないリンクが弱い。私が収集したものから、所有されていない自己 nilになりませんが、弱い自己は存在する可能性があり、所有されていない自己はぶら下がりポインタにつながる可能性があります... Objective-Cで悪名高いもの。それが役に立てば幸い

「未定義の弱い参照と未所有の参照は同様に動作しますが、同じではありません。」

弱い参照のような所有されていない参照は、参照されているオブジェクトの保持カウントを増やしません。ただし、Swiftでは、所有されていない参照にはOptionalにならないという追加の利点があります。これにより、オプションのバインディングを使用するよりも、管理が容易になります。これは、暗黙的にアンラップされたオプションとは異なりません。さらに、所有されていない参照はゼロ以外です。つまり、オブジェクトの割り当てが解除されても、ポインタはゼロになりません。つまり、所有されていない参照を使用すると、ポインタがぶら下がることがあります。。私と同じようにObjective-Cの日を覚えているオタクのために、所有されていない参照はunsafe_unretained参照にマッピングされます。

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

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

どちらも保持サイクルを解除するために使用できます。では、いつ使用するのですか?

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

「その参照が有効期間中のどこかの時点でnilになることが有効な場合は、弱い参照を使用します。逆に、初期化中に設定された参照はnilにならないことがわかっている場合は、所有されていない参照を使用してください。」


0
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        self.navigationController?.pushViewController(controller, animated: true)

    }

}



import UIKit
class AnotherViewController: UIViewController {

    var name : String!

    deinit {
        print("Deint AnotherViewController")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print(CFGetRetainCount(self))

        /*
            When you test please comment out or vice versa

         */

//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//


//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//


//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }


        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)

        clouser(string: "") {  (boolValue)  in
            print("some")
            print(CFGetRetainCount(self))

        }

    }


    func clouser(string: String, completion: @escaping (Bool) -> ()) {
        // some heavy task
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            completion(true)
        }

    }

}

よくわからない場合 [unowned self] は、 [weak self]

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.