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

问题描述 投票:431回答:8

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

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

line 64 of ViewController.swift of the Swift Weather app,我不使用[unowned self]。但是我通过使用@IBOutletself.temperature这样的qazxswpo来更新UI。这可能没关系,因为我定义的所有self.loadingIndicators都是@IBOutlet。但为了安全起见,我们是否应该总是使用weak

[unowned self]
ios swift automatic-ref-counting
8个回答
828
投票

不,肯定有时候你不想使用class TempNotifier { var onChange: (Int) -> Void = {_ in } var currentTemp = 72 init() { onChange = { [unowned self] temp in self.currentTemp = temp } } } 。有时你希望闭包捕获self,以确保在调用闭包时它仍然存在。

示例:发出异步网络请求

如果您正在进行异步网络请求,则确实希望闭包在请求完成时保留[unowned self]。否则该对象可能已被取消分配,但您仍希望能够处理完成请求。

何时使用selfunowned self

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

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

Specifically in the example from the video

在幻灯片上的示例中,[weak self]通过TempNotifier成员变量拥有闭包。如果他们没有宣布onChangeself,那么封闭也将拥有unowned创建一个强大的参考周期。

selfunowned之间的区别

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


176
投票

2016年11月更新

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

原始答案

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

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

here

Scenarios

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

  1. 闭包具有相同的生命周期,因此只有在变量可达时才能访问闭包。变量和闭包具有相同的寿命。在这种情况下,您应该将引用声明为unowned。一个常见的例子是swift weak vs unowned在许多小封闭的例子中使用,这些封闭在父母的上下文中做某事并且在其他任何地方没有被引用并不比他们的父母长寿。
  2. 闭包生命周期独立于变量之一,当变量不再可达时,仍然可以引用闭包。在这种情况下,您应该将引用声明为弱并在使用之前验证它不是nil(不要强制解包)。一个常见的例子是[unowned self],你可以在闭包引用一个完全不相关(生命周期)的委托对象的一些例子中看到。

Actual Usage

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

[weak delegate]

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

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

你会发现更多关于unownedQuoting Joe Groff from twitter内部工作*

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


83
投票

我想我会专门为视图控制器添加一些具体的例子。许多解释,不仅仅是Stack Overflow,都非常好,但是我用现实世界的例子做得更好(@drewag在这方面有一个良好的开端):

  • 如果你有一个闭包来处理网络请求的响应使用*,因为它们很长寿。视图控制器可以在请求完成之前关闭,因此在调用闭包时weak不再指向有效对象。
  • 如果你有一个闭包来处理按钮上的事件。这可能是self,因为一旦视图控制器消失,按钮和它可能从unowned引用的任何其他项目同时消失。闭合块也将同时消失。 self

67
投票

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

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

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

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


48
投票

来自https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/AutomaticReferenceCounting.html的精彩报价描述了美味的细节:

Apple Developer Forums vs unowned vs unowned(safe)

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

-Ofast vs unowned

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

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

Reasons

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

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

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

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

兴奋,是吧?


33
投票

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

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

unonwed

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

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


3
投票

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

  • 弱引用始终是可选类型,并且在它们引用的实例被释放时自动变为nil。
  • 如果捕获的引用永远不会变为nil,则应始终将其捕获为无主引用,而不是弱引用

示例 -

Apple-doc

0
投票

闭包的强参考周期

如果为类实例的属性分配闭包,并且该闭包的主体捕获实例,则也会发生强引用循环。这种捕获可能是因为闭包的主体访问实例的属性,例如 // 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) } ,或者因为闭包调用实例上的方法,例如self.someProperty。在任何一种情况下,这些访问都会导致闭包“捕获”self.someMethod(),从而创建一个强大的参考周期。有关self的更多信息

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

  • 使用more here SO引用,只要它对该引用有效,在其生命周期的某个时刻成为weak
  • 当您知道在初始化期间设置引用后永远不会是nil时,请使用unowned引用。

nil


0
投票

如果以上都不合理:

tl;博士

就像Doc with example一样,如果你可以保证在使用时它的引用不会为零,那就使用无主的。如果没有,那么你应该使用弱。

说明:

我在下面检索了以下内容:implicitly unwrapped optional。从我收集的内容来看,无主的自我不可能是零,但自我可能是弱的,无主的自我可以导致悬垂的指针......在Objective-C中臭名昭着。希望能帮助到你

“UNOWNED弱和无主参考表现相似,但不一样。”

无主引用(如弱引用)不会增加被引用对象的保留计数。但是,在Swift中,无主引用具有不是可选的附加好处。这使得它们更易于管理,而不是诉诸于使用可选绑定。这与Implicitly Unwrapped Optionals没有什么不同。此外,无主引用是非归零的。这意味着当对象被释放时,它不会将指针清零。这意味着在某些情况下,使用无主引用会导致悬空指针。对于那些像我一样记住Objective-C天的书呆子,无主参考映射到unsafe_unretained引用。

这是令人困惑的地方。

弱和无主引用都不会增加保留计数。

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

根据Apple的文档:

“只要对于该引用有效,就使用弱引用在其生命周期的某个时刻变为零。相反,当你知道在初始化过程中设置引用永远不会为nil时,使用无主引用。“

© www.soinside.com 2019 - 2024. All rights reserved.