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

Jak*_*Lin 455 ios automatic-ref-counting swift

在WWDC 2014会议403 中级Swift成绩单中,有以下幻灯片

在此输入图像描述

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

Swift Weather应用程序的ViewController.swift的第64行,我不使用[unowned self].但我通过使用一些@IBOutletself.temperature和更新UI 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
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

dre*_*wag 851

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

示例:发出异步网络请求

如果要进行异步网络请求,需要self在请求完成时保留闭包.否则该对象可能已取消分配,但您仍希望能够处理完成请求.

何时使用unowned selfweak self

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

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

特别是在视频的例子中

在幻灯片的示例中,TempNotifier通过onChange成员变量拥有闭包.如果他们没有申报selfunowned,关闭也将自己self创造一个有力的参考周期.

unowned和之间的区别weak

之间的区别unownedweakweak被声明为可选,而unowned不是.通过声明它weak你可以处理在某些时候它可能在闭包内部的情况.如果您尝试访问unowned恰好为nil 的变量,则会导致整个程序崩溃.因此,只有unowned当你肯定变量将永远存在于闭包附近时才使用

  • 在异步网络请求中使用`[weak self]`的情况是在**视图控制器**中,其中该请求用于填充视图.如果用户退出,我们不再需要填充视图,也不需要对视图控制器的引用. (73认同)
  • @robdashnash,使用无主自我的好处是你不必打开一个可选的代码,如果你在设计上肯定知道它永远不会是零,那么它就是不必要的代码.最终,无主的自我用于简洁,也许也可以作为未来开发人员的暗示,你永远不会期望零价值. (15认同)
  • 我有一点困惑。“unowned”用于“非可选”,而“weak”用于“可选”,那么我们的“self”是“可选”还是“非可选”? (2认同)

Umb*_*ndi 185

2016年11月更新

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

原始答案

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

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

迅速弱与无主

方案

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

  1. 闭包具有相同的生命周期,因此只有在变量可达时才能访问闭包.变量和闭包具有相同的寿命.在这种情况下,您应该将引用声明为unowned.一个常见的例子是[unowned self]在许多小闭包的例子中使用,这些闭包在父母的上下文中做某事,而在其他任何地方都没有被引用不会比父母长.

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

实际用途

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

从推特上引用Joe Groff:

无主是更快,允许不变性和非可选性.

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

你会在这里找到更多关于无主*内部运作的信息.

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

  • 我已经厌倦了听到鹦鹉的解释"如果自我可能是零那么使用周,当它永远不能为零时使用无主".好的,我们得到了它 - 听了一百万次!这个答案实际上深入挖掘了自己在简单英语中何时可以为零,这直接回答了OP的问题.谢谢你这个很棒的解释!! (23认同)

pos*_*sen 94

我想我会专门为视图控制器添加一些具体的例子.许多解释,不仅仅是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.
     }
    
    Run Code Online (Sandbox Code Playgroud)

  • 这个需要更多的赞成.两个可靠的示例显示了按钮按钮关闭在视图控制器的生命周期之外不会存在,因此可以使用无主,但大多数更新UI的网络调用都需要较弱. (16认同)
  • 因此,为了澄清一下,在闭包中调用self时,我们是否总是使用unown或weak?还是有一段时间我们不会称之为弱者/无人?如果是这样,您也可以提供一个示例吗? (2认同)

Ten*_*Jay 68

如果封闭使用自我可能是零[弱自我].

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

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

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


Val*_*gin 49

以下是来自Apple Developer论坛的精彩报价,描述了美味的细节:

unownedvs unowned(safe)vsunowned(unsafe)

unowned(safe)是一个非拥有的引用,在访问时声明对象仍然存在.它有点像一个弱的可选引用,x!每次访问它时都会隐式解包. unowned(unsafe)就像__unsafe_unretained在ARC 中一样,它是一个非拥有的引用,但没有运行时检查对象在访问时仍然存活,因此悬空引用将进入垃圾内存. unowned始终是unowned(safe)当前的同义词,但目的是unowned(unsafe)-Ofast 禁用运行时检查时,它将在构建中进行优化.

unowned VS weak

unowned实际上使用的实现比简单得多weak.Native Swift对象带有两个引用计数,unowned 引用会使无主引用计数而不是 引用计数.当引用计数达到零时,该对象被取消初始化,但在无主参考计数也达到零之前,它实际上不会被释放 .当存在无主引用时,这会使内存保持稍长时间,但这在unowned使用时通常不会成为问题,因为相关对象应该具有接近相等的生命周期,并且它比侧面更简单且开销更低.基于表的实现,用于归零弱引用.

更新:在现代斯威夫特weak在内部使用的相同的机制unowned.所以这种比较是不正确的,因为它将Objective-C weak与Swift 进行了比较unonwed.

原因

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

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

对象拥有或拥有的引用会发生什么?当它被取消初始化时,它们的生命周期是否与对象分离,或者它们的内存是否也会保留,直到在释放最后一个无主引用后释放对象为止?

一旦对象的最后一个强引用被释放,就会释放该对象拥有的所有资源,并且运行它的deinit.无主引用只保留内存,而不是带引用计数的头,它的内容是垃圾.

兴奋,是吧?


Saf*_*ive 36

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

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

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

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

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

  • 那么在新的变化之后,是否有一个时候不能使用“weak”来代替“unowned”? (2认同)

Jac*_*ack 5

根据苹果文档

  • 弱引用始终是可选类型,并在它们引用的实例被释放时自动变为 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)
     }
Run Code Online (Sandbox Code Playgroud)