我们应该在Swift中总是使用封闭

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

在这里输入图像描述

在这种情况下,发言者说,如果我们不使用[unowned self] ,那将是一个内存泄漏。 这是否意味着我们应该总是在关闭中使用[unowned self]

在Swift Weather应用程序的ViewController.swift的第64行 ,我没有使用[unowned self] 。 但我通过使用self.temperatureself.loadingIndicator等一些@IBOutlet来更新UI。 这可能是好的,因为我定义的所有@IBOutlet都是weak 。 但为了安全起见,我们是否应该总是使用[unowned self]

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

不,绝对有时候你不想使用[unowned self] 。 有时你想让闭包捕获自己,以确保在闭包被调用的时候它仍然在附近。

示例:发出异步网络请求

如果你正在做一个异步网络请求,你希望闭包在请求结束时保持self 。 该对象可能已被解除分配,但您仍然希望能够处理请求完成。

何时使用unowned selfweak self

你唯一想要使用[unowned self]或者[weak self]的时候就是创造一个强大的参考周期 。 一个强有力的参照周期是当一个对象最终拥有对方(可能通过第三方)拥有一个所有权循环时,因此它们将永远不会被释放,因为它们都确保对方坚持到底。

在一个闭包的特定情况下,你只需要认识到在它内部引用的任何变量都被闭包“拥有”。 只要封闭在附近,这些物体就能保证在周围。 阻止这种所有权的唯一方法就是做[unowned self][weak self] 。 所以如果一个类拥有一个闭包,那个闭包占据了这个类的强大引用,那么在闭包和这个类之间有一个很强的引用循环。 这也包括如果类拥有拥有闭包的东西。

特别是在视频的例子

在幻灯片的示例中, TempNotifier通过onChange成员变量拥有闭包。 如果他们没有宣布selfunowned ,封闭也将自己创造一个强大的参照周期。

unownedunowned之间的区别

unownedunowned之间的区别在于, unowned unowned是不可能的。 通过宣称它weak你可以处理在某些时候它可能在关闭内的情况。 如果你试图访问一个unowned变量,它将会导致整个程序崩溃。 所以只有当你肯定的时候才使用unowned变量总是在封闭的周围

更新11/2016

我写了一篇关于这个扩展这个答案的文章(查看SIL来理解ARC所做的), 在这里检查一下 。

原始答案

以前的答案并没有真正规定什么时候使用一个,为什么,所以让我添加一些东西。

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

迅速软弱与无主

方案

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

  1. 闭包拥有与变量相同的生命周期,所以闭包只有在变量可到时才可以访问 。 变量和闭包有相同的生命周期。 在这种情况下,您应该声明该参考为无主 。 一个常见的例子是许多小封闭例子中使用的[unowned self] ,他们在父母的背景下做了一些事情,而在其他任何地方都没有被引用,他们的父母没有活过。

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

实际使用

那么,你将会/应该实际使用大部分时间吗?

引用Joe Groff的推特 :

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

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

你会在这里找到更多关于无主*内部工作。

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

如果自我可能在封闭使用[弱自我]

如果封闭使用[无主的自我], 自我永远不会成为零。

Apple Swift文档有一个很大的部分,用图像来解释使用无主闭包的区别:

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

这里是来自苹果开发者论坛的精彩报价描述了美味的细节:

unownedunowned(safe)unowned(unsafe)

unowned(safe)是一个非拥有的参考,断言访问该对象仍然活着。 这有点像一个微弱的可选参考,它是用x!隐式解开的x! 每次访问。 unowned(unsafe)就像ARC中的__unsafe_unretained – 它是一个非拥有的引用,但没有运行时检查对象在访问时仍然活着,所以悬挂的引用将进入垃圾内存。 unowned的总是当前unowned(safe)的同义词,但其目的是在运行时检查被禁用时,在-Ofast构建中将被优化为unowned(unsafe)

unowned vs weak

unowned实际上使用比weak的更简单的实现。 本地Swift对象携带两个引用计数, unowned引用会冲突无主引用计数而不是引用计数 。 该对象在其引用计数达到零时被去初始化,但实际上不被释放,直到无主引用计数也达到零。 这会导致内存被保持稍微长一点,当有无主的引用,但是这通常不是一个问题,当使用unowned时,因为相关的对象应该有几乎相等的生命期,无论如何,它是更简单,更低的开销用于清零弱引用的基于边表的实现。

更新:在现代Swift weak内部使用与unowned的相同的机制 。 所以这个比较是不正确的,因为它比较了Objective-C和Swift之间的unonwed

原因

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

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

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

只要对象的最后一个强引用被释放,对象拥有的所有资源就会被释放,并且它的deinit被运行。 无主引用只保留活着的内存 – 除了引用计数的头部之外,其内容是垃圾。

兴奋吧?

我想我会添加一些具体的例子视图控制器。 许多解释,不仅在这里堆栈溢出,真的很好,但我更好地与现实世界的例子(@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. } 

根据苹果文件

  • 弱引用总是一个可选的类型,并且当它们引用的实例被释放时自动变为零。

  • 如果捕获的引用永远不会成为零,那么它应该始终作为一个无主的引用来捕获,而不是一个弱引用

示例 –

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