我们总是在Swift中使用[unowned self]内部封闭

时间:2014-06-20 05:11:51

标签: ios swift automatic-ref-counting

在WWDC 2014会话403 Intermediate Swifttranscript中,有以下幻灯片

enter image description here

在这种情况下,发言人说,如果我们不在那里使用[unowned self],那将是内存泄漏。这是否意味着我们应该始终在封闭内使用[unowned self]

line 64 of ViewController.swift of the Swift Weather app,我不会使用[unowned self]。但我使用@IBOutletself.temperature之类的self.loadingIndicator来更新用户界面。可能没问题,因为我定义的所有@IBOutlet都是weak。但为安全起见,我们是否应始终使用[unowned self]

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

10 个答案:

答案 0 :(得分:807)

不,肯定有时候你不想使用[unowned self]。有时你希望闭包捕获self,以确保在调用闭包时它仍然存在。

示例:发出异步网络请求

如果您要发出异步网络请求,执行希望闭包在请求完成时保留self。该对象可能已经被取消分配,但您仍然希望能够处理完成请求。

何时使用unowned selfweak self

您真正想要使用[unowned self][weak self]的唯一时间是创建strong reference cycle。一个强大的参考周期是当存在一个所有权循环,其中对象最终彼此拥有(可能通过第三方),因此它们永远不会被释放,因为它们都确保彼此坚持。

在闭包的特定情况下,您只需要意识到在其中引用的任何变量都会被拥有"关闭。只要封闭物周围,这些物体就可以保证在周围。阻止该所有权的唯一方法是执行[unowned self][weak self]。因此,如果一个类拥有一个闭包,并且该闭包捕获了对该类的强引用,那么在闭包和类之间有一个强大的引用循环。这还包括如果该类拥有拥有该闭包的东西。

特别是在视频的例子中

在幻灯片的示例中,TempNotifier通过onChange成员变量拥有闭包。如果他们没有将self声明为unowned,则关闭也会拥有self,从而创建一个强大的参考周期。

unownedweak

之间的差异

unownedweak之间的区别在于weak被声明为可选,unowned则不是。通过声明它weak,您可以处理在某些时候它在闭包内可能为零的情况。如果您尝试访问恰好为nil的unowned变量,则会导致整个程序崩溃。因此,只有当你肯定变量将在闭包围绕

时始终存在时才使用unowned

答案 1 :(得分:169)

2016年11月更新

我写了一篇关于此问题的文章,扩展了这个答案(查看SIL以了解ARC的作用),查看here

原始答案

之前的答案并没有真正给出关于何时使用其中一个的简单规则以及为什么,所以让我添加一些内容。

无主论者或弱论者归结为变量的生命周期和引用它的闭包的问题。

swift weak vs unowned

方案

您可以有两种可能的情况:

  1. 闭包具有与变量相同的生命周期,因此只有在变量可达之前,闭包才能到达。变量和闭包具有相同的寿命。在这种情况下,您应该将引用声明为 unowned 。一个常见的例子是许多小封闭示例中使用的[unowned self],它们在父级的上下文中执行某些操作,并且在其他任何地方都没有被引用,并且不会超过父级。

  2. 闭包生命周期与变量之一无关,当变量不再可达时,仍然可以引用闭包。在这种情况下,您应该将引用声明为并在使用它之前验证它不是nil(不要强制解包)。一个常见的例子是[weak delegate],你可以在一些闭包引用一个完全不相关的(终身明智的)委托对象的例子中看到。{/ p>

  3. 实际用途

    那么,大多数时候你会/你应该使用哪种?

    Quoting Joe Groff from twitter

      

    无主是更快,允许不变性和非可行性。

         

    如果您不需要弱,请不要使用它。

    您可以找到有关无内容*内部工作here的更多信息。

    * 通常也称为无主(安全),表示在访问无主参考之前执行运行时检查(导致无效引用崩溃)。

答案 2 :(得分:70)

我想我会为视图控制器专门添加一些具体的例子。许多解释,不仅仅是Stack Overflow,都非常好,但是我用现实世界的例子做得更好(@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.
     }
    

答案 3 :(得分:67)

如果 self 在闭包中可能为零,请使用 [弱自我]

如果 self 永远不会在闭包中使用 [无主自我]

Apple Swift文档中有一个很棒的部分,其中的图片解释了在闭包中使用无主之间的区别:

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

答案 4 :(得分:48)

来自Apple Developer Forums的精彩引言描述了美味的细节:

unowned vs unowned(safe) vs unowned(unsafe)

  

unowned(safe)是一个非拥有的引用,在访问时断言   对象还活着。它有点像弱可选参考   每次访问时都隐含地用x!打开。   unowned(unsafe)就像ARC中的__unsafe_unretained一样 - 它是非拥有者   引用,但是没有运行时检查对象是否仍然存活   在访问时,悬空引用将进入垃圾记忆。   unowned目前始终是unowned(safe)的同义词,但是   意图是它将优化到unowned(unsafe)中的-Ofast   在禁用运行时检查时构建。

unowned vs weak

  

unowned实际上使用的实现比weak简单得多。   Native Swift对象带有两个引用计数,unowned   引用会增加 无主引用次数而不是 strong   参考计数。当 引用时,该对象将被取消初始化   count 达到零,但实际上它没有被释放,直到    无主引用次数也会达到零。这会导致内存   当有无主参考时,保持稍长一点,但那   使用unowned时通常不会出现问题,因为相关   对象应该具有接近相等的生命期,并且它更简单   并且比用于的基于侧表的实现开销更低   归零弱参考。

更新:在现代Swift weak internally uses the same mechanism as unowned does中。因此,这种比较是不正确的,因为它将Objective-C weak与Swift unonwed进行了比较。

的原因

  

拥有引用达到0后保持内存存活的目的是什么?如果代码试图执行某些操作会发生什么   在取消初始化之后使用无主参考的对象?

     

在   内存保持活动状态,以便其保留计数仍然可用。   这样,当有人试图保留强烈的引用时   无主对象,运行时可以检查强引用计数   大于零是为了确保保留它是安全的   对象

     

对象拥有或拥有的引用会发生什么?当它被去初始化时,它们的寿命是否与对象分离   是他们的记忆也保留,直到对象被解除分配后   最后一个无主参考发布了吗?

     

对象拥有的所有资源都会在对象发布后立即释放   最后一个强引用被释放,它的deinit运行。无主   引用只保留内存,而不是标题   引用计数,其内容是垃圾。

很兴奋,是吗?

答案 5 :(得分:33)

这里有一些很棒的答案。但最近对Swift如何实现弱引用的更改应该改变每个人的弱自我与无主自我使用决策。以前,如果你需要使用无主自我的最佳表现优于弱自我,只要你可以确定自我永远不会是零,因为访问无主自我比访问弱自我快得多。

但Mike Ash已经记录了Swift如何更新弱变量的实现以使用边表以及这如何显着改善弱自我表现。

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

既然对弱者自身没有显着的性能损失,我相信我们应该默认使用它。弱自我的好处是它是可选的,这使得编写更正确的代码变得容易得多,它基本上是Swift是如此优秀语言的原因。你可能认为你知道哪些情况对于使用无主的自我是安全的,但是我回顾很多其他开发人员代码的经验是,大部分都不是。我已经解决了许多崩溃问题,其中无主自我被解除分配,通常是在取消分配控制器后后台线程完成的情况下。

错误和崩溃是编程中最耗时,最痛苦和最昂贵的部分。尽力编写正确的代码并避免使用它们。我建议永远不要强制打开选项,永远不要使用无主的自我而不是弱自我。你不会失去任何错过任何时间的力量解开和无主的自我实际上是安全的。但是,通过消除难以发现和调试崩溃和错误,您将获得很多收益。

答案 6 :(得分:3)

根据Apple-doc

  
      
  • 弱引用始终是可选类型,并且是自动的   当他们引用的实例被解除分配时变为零。

  •   
  • 如果捕获的参考永远不会变为零,则应始终将其捕获为无主参考,而不是弱参考

  •   

示例 -

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

答案 7 :(得分:0)

关闭的强大参考周期

如果将闭包分配给类实例的属性,并且该闭包的主体捕获该实例,则也会发生强引用循环。之所以会发生这种捕获,是因为闭包的主体访问了实例的属性,例如<?xml version="1.0" encoding="utf-8"?> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyWidth="8.5%p" android:keyHeight="7%p" android:keyEdgeFlags="left"> <Row> <Key android:keyLabel="Q" android:keyEdgeFlags="left" /> <Key android:keyLabel="W" /> <Key android:keyLabel="E" /> <Key android:keyLabel="R" /> <Key android:keyLabel="T" /> <Key android:keyLabel="Z" /> <Key android:keyLabel="U" /> <Key android:keyLabel="I" /> <Key android:keyLabel="O" /> <Key android:keyLabel="P" /> <Key android:codes="55000" android:keyIcon="@drawable/del_keyboard" android:isRepeatable="true" android:keyWidth="15%p" android:keyEdgeFlags="right" /> </Row> <Row> <Key android:keyLabel=" " android:keyHeight="0px" android:keyWidth="0px" android:keyEdgeFlags="left"/> <Key android:keyLabel="A" /> <Key android:keyLabel="S" /> <Key android:keyLabel="D" /> <Key android:keyLabel="F" /> <Key android:keyLabel="G" /> <Key android:keyLabel="H" /> <Key android:keyLabel="J" /> <Key android:keyLabel="K" /> <Key android:keyLabel="L" android:keyEdgeFlags="right" /> </Row> <Row> <Key android:keyLabel=" " android:keyHeight="0px" android:keyEdgeFlags="left" android:keyWidth="0px"/> <Key android:keyLabel=" " android:keyHeight="0px" android:keyWidth="0px"/> <Key android:keyLabel="Y" /> <Key android:keyLabel="X" /> <Key android:keyLabel="C" /> <Key android:keyLabel="V" /> <Key android:keyLabel="B" /> <Key android:keyLabel="N" /> <Key android:keyLabel="M" android:keyEdgeFlags="right" /> </Row> <Row> <Key android:keyLabel=" " android:keyHeight="0px" android:keyWidth="0px" android:keyEdgeFlags="left"/> <Key android:keyLabel=" " android:keyHeight="0px" android:keyWidth="0px"/> <Key android:keyLabel=" " android:keyHeight="0px" android:keyWidth="0px"/> <Key android:codes="55001" android:keyEdgeFlags="left" android:keyIcon="@drawable/white_space_keyboard" android:keyWidth="50%p" /> </Row> ,或者是因为闭包调用了实例上的方法,例如self.someProperty。在任何一种情况下,这些访问都会导致闭包“捕获” self.someMethod(),从而形成一个强大的参考周期。有关capturing values in a closure

的更多信息

Swift为这个问题提供了一种优雅的解决方案,称为关闭捕获列表。捕获列表定义了在闭包体内捕获一种或多种参考类型时要使用的规则。与两个类实例之间的强引用循环一样,您可以将每个捕获的引用声明为selfweak引用,而不是unowned引用。 strongweak的适当选择取决于代码不同部分之间的关​​系。 more here SO

  • 只要有效,unowned引用就可以在其生命周期中的某个时刻成为weak
  • 当您知道引用在初始化过程中被设置后将永远永远不会成为nil时,请使用unowned引用。

Doc with example

答案 8 :(得分:0)

如果以上均不可行:

tl; dr

  

就像implicitly unwrapped optional一样,如果可以保证   引用在使用时不会为零,请使用无所有权。   如果没有,那么您应该使用弱。

说明:

我在以下位置检索了以下内容:weak unowned link。从我收集的数据来看,无主的自我不能为零,但弱的自我可以为零,而无主的自我可以导致悬空的指针……这在Objective-C中是臭名昭著的。希望对您有帮助

  

“ UNOWNED弱引用和无主引用的行为类似,但不相同。”

无主引用,类似弱引用,不增加被引用对象的保留计数。但是,在Swift中,无主引用具有不是可选项的优点。这使他们更易于管理,而不是诉诸于使用可选绑定。这与隐式展开的Optionals不同。此外,无主引用是非归零这意味着在释放对象时,它不会使指针归零。这意味着,在某些情况下,使用未拥有的引用可能会导致指针悬空。对于那些像我一样记得Objective-C的书呆子,未拥有的引用映射到unsafe_unretained引用。

这是令人困惑的地方。

  

弱引用和无所有权引用都不会增加保留计数。

它们都可以用来破坏保留周期。那么我们什么时候使用它们呢?

根据 Apple的文档

  

“只要有效,只要使用弱引用即可使该引用在其生命周期中的某个时刻变为零。相反,当您知道在初始化期间将其设置为永远不会为零时,请使用无主引用。”

答案 9 :(得分: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]