我们是否应该在Swift的闭包内部始终使用[unown self]


467

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

在此处输入图片说明

演讲者说,在这种情况下,如果我们不在[unowned self]那里使用它,将会导致内存泄漏。这是否意味着我们应该始终使用[unowned self]内部闭包?

Swift Weather应用程序的ViewController.swift的第64行,我没有使用[unowned self]。但是我通过使用@IBOutlet诸如self.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
    }
  }
}

图片链接已损坏
Daniel Gomez Rico

@ DanielG.R。谢谢,我能看到。i.stack.imgur.com/Jd9Co.png
Jake Lin)

2
除非我弄错了,否则幻灯片中给出的示例是错误的- onChange应该是一个[weak self]闭包,因为它是一个公共(内部,但仍然是)属性,因此另一个对象可以获取并存储闭包,并保持TempNotifier对象不存在(如果using对象onChange直到TempNotifier通过自己对(TempNotifier)的弱引用看到s 消失后才放开闭包。如果var onChange …是的private var onChange …[unowned self]将是正确的。 我对此不是100%肯定的;如果我错了,请有人纠正我。
Slipp D. Thompson

@Jake Lin`var onChange:(Int)-> Void = {}`花括号代表空的闭包吗?与使用[]?定义空数组相同 我在Apple文档中找不到解释。
bibscy

@bibscy是,{}是空闭包(闭包的实例),默认设置(不执行任何操作),(Int) -> Void是闭包定义。
杰克·林

Answers:


871

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

示例:发出异步网络请求

如果要发出异步网络请求,则确实希望self在请求完成时保留关闭。否则该对象可能已被释放,但是您仍然希望能够处理请求完成。

何时使用unowned selfweak self

唯一的一次,你真的想用[unowned self]或者[weak self]是当你将创建一个有力的参考周期。一个强大的参考周期是当所有权循环出现时,对象最终会彼此拥有(可能是通过第三方),因此,由于它们都确保彼此粘在一起,因此永远不会将它们释放。

在闭包的特定情况下,您只需要意识到闭包内部拥有的所有引用变量都由闭包“拥有”。只要闭包在周围,这些对象就可以保证在周围。停止所有权的唯一方法是执行[unowned self][weak self]。因此,如果一个类拥有一个闭包,并且该闭包捕获了对该类的强引用,那么在闭包和该类之间便拥有一个强引用周期。这还包括类是否拥有某个拥有闭包的东西。

特别是在视频示例中

在幻灯片的示例中,TempNotifier通过onChange成员变量拥有闭包。如果未声明selfunowned,则闭包也将自己self创建一个强大的参考周期。

unowned和之间的区别weak

unowned和之间的区别weak是,weak声明为Optional unowned却不是。通过声明它,weak您可以处理某些情况下它在闭包内部可能为零的情况。如果您尝试访问一个unowned恰好为nil 的变量,它将使整个程序崩溃。因此,仅unowned当您肯定变量在闭包周围时始终在周围时才使用


1
你好 好答案。我正在努力了解无主的自我。对我来说,仅使用“ self成为可选项”来使用weakSelf的原因还不够。为什么我特别想用“无主的自我” stackoverflow.com/questions/32936264/...

19
@robdashnash,使用无主self的优点是您不必解开一个可选的选项,如果您通过设计肯定会知道它是无用的,那么该选项可能是不必要的代码。最终,无所有权的自我是为了简洁起见,也可能向未来的开发人员暗示您永远不会期望零值。
drewag

77
[weak self]在异步网络请求中使用的一种情况是在视图控制器中,该请求用于填充视图。如果用户退出,则不再需要填充视图,也不需要引用视图控制器。
大卫·詹姆斯

1
weak引用也设置为nil对象被释放时的时间。unowned引用不是。
BergQuester

1
我有点困惑。unowned用于non-Optional同时weak用于Optional所以我们selfOptional还是non-optional
穆罕默德·纳亚布

193

更新11/2016

我写了一篇有关扩展此答案的文章(研究SIL以了解ARC的作用),请在此处查看

原始答案

前面的答案并没有真正给出何时何时使用另一个为什么的简单明了的规则,所以让我补充一些内容。

无主或虚弱的讨论可以归结为变量的生存期以及引用它的闭包的问题。

迅速虚弱vs无主

情境

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

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

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

实际用法

那么,您实际上会/应该在大多数时间使用?

从推特引用乔·格罗夫

无所有权的速度更快,并允许不变性和非选择性。

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

您可以在这里找到更多有关无主*内部工作的信息

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


26
我厌倦了听到鹦鹉的解释:“如果自我可能为零,则使用一周,如果永远不会为零,则使用无主”。好吧,我们明白了-听到了一百万遍!这个答案实际上用简单的英语深入探讨了自我何时可以为零的问题,这直接回答了OP的问题。感谢您的精彩解释!!
TruMan1

感谢@ TruMan1,我实际上是在为此写一篇帖子,该帖子很快就会出现在我的博客上,并使用链接更新答案。
Umberto Raimondi

1
好的答案,非常实用。我受到鼓舞,现在将一些对性能敏感的弱变量切换为无人值守。
original_username

“关闭寿命与变量之一无关”您在这里有错字吗?
亲爱的

1
如果闭包始终与父对象具有相同的生存期,则销毁对象时,引用计数是否仍会受到照顾?为什么您不能在这种情况下只使用“自我”,而不用为无主或软弱的人烦恼?
LegendLength

105

我以为我会为视图控制器添加一些具体示例。不仅在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.
     }

17
这需要更多的支持。有两个可靠的示例显示了如何在视图控制器的生命周期之外不存在按钮关闭的关闭,因此可以使用无主按钮,但是大多数更新UI的网络调用都需要弱一些。
蒂姆·富夸

2
因此,为了澄清一下,在闭包中调用self时,我们是否总是使用unown或weak?还是有一段时间我们不会称之为弱者/无人?如果是这样,您也可以提供一个示例吗?
路加福音

非常感谢。
肖恩·贝克

1
这使我对[弱自我]和[无主自我]有了更深入的了解,非常感谢@possen!
汤米

这很棒。如果我有一个基于用户交互的动画,但要花一些时间才能完成,该怎么办。然后用户移至另一个viewController。我想在那种情况下我应该仍然使用weak而不是unowned正确?
亲爱的


50

以下是来自苹果开发者论坛的精彩语录,描述了美味的细节:

unownedvs unowned(safe)vsunowned(unsafe)

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

unownedweak

unowned实际上使用的实现比weak。本机Swift对象带有两个引用计数,并且unowned 引用会碰撞未拥有的引用计数,而不是 引用计数。当对象的引用计数达到零时,该对象将被取消初始化,但是直到未拥有的引用计数也达到零时才真正释放 该对象。这会导致在存在无主引用的情况下将内存保留的时间稍长一些,但这通常在以下情况下不会出现问题:unowned 之所以使用,是因为相关对象无论如何都应该具有几乎相等的生存期,并且比用于弱引用清零的基于边表的实现要简单得多,开销也更低。

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

原因

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

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

对象拥有的拥有或未拥有的引用会发生什么?当对象被取消初始化时,它们的生命周期是否与对象分离了?或者在释放最后一个无主引用之后对象被重新分配之前,它们的内存也得以保留吗?

对象的最后一个强引用释放后,对象的所有资源都将释放,并且其deinit将运行。无主引用仅使内存保持活动状态-除了带有引用计数的标头之外,其内容是垃圾。

激动,是吗?


38

这里有一些很好的答案。但是,最近Swift实施弱引用的方式的变化应该改变每个人的弱自我决策与无所有权自我使用决策。以前,只要您可以确定自己永远不会为零,那么使用无主自我的最佳性能要优于无主自我,因为访问无主自我比访问无主自我要快得多。

但是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。

  • 如果捕获的引用永远不会为零,则应始终将其捕获为未拥有的引用,而不是弱引用

范例-

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

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

说明:

我在以下位置检索了以下内容:弱无主链接。根据我的收集,无主的自我不能为零,但弱的自我可以为零,无主的自我可以导致悬而未决的指针……这在Objective-C中是臭名昭著的。希望能帮助到你

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

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

这是令人困惑的地方。

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

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

根据Apple的文档

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


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]

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.