我们应该在Swift中总是使用封闭
在WWDC 2014会议403 中级Swift和成绩单中 ,有以下幻灯片
在这种情况下,发言者说,如果我们不使用[unowned self]
,那将是一个内存泄漏。 这是否意味着我们应该总是在关闭中使用[unowned self]
?
在Swift Weather应用程序的ViewController.swift的第64行 ,我没有使用[unowned self]
。 但我通过使用self.temperature
和self.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 self
或weak self
你唯一想要使用[unowned self]
或者[weak self]
的时候就是创造一个强大的参考周期 。 一个强有力的参照周期是当一个对象最终拥有对方(可能通过第三方)拥有一个所有权循环时,因此它们将永远不会被释放,因为它们都确保对方坚持到底。
在一个闭包的特定情况下,你只需要认识到在它内部引用的任何变量都被闭包“拥有”。 只要封闭在附近,这些物体就能保证在周围。 阻止这种所有权的唯一方法就是做[unowned self]
或[weak self]
。 所以如果一个类拥有一个闭包,那个闭包占据了这个类的强大引用,那么在闭包和这个类之间有一个很强的引用循环。 这也包括如果类拥有拥有闭包的东西。
特别是在视频的例子
在幻灯片的示例中, TempNotifier
通过onChange
成员变量拥有闭包。 如果他们没有宣布self
是unowned
,封闭也将自己创造一个强大的参照周期。
unowned
与unowned
之间的区别
unowned
与unowned
之间的区别在于, unowned
unowned
是不可能的。 通过宣称它weak
你可以处理在某些时候它可能在关闭内的情况。 如果你试图访问一个unowned
变量,它将会导致整个程序崩溃。 所以只有当你肯定的时候才使用unowned
变量总是在封闭的周围
更新11/2016
我写了一篇关于这个扩展这个答案的文章(查看SIL来理解ARC所做的), 在这里检查一下 。
原始答案
以前的答案并没有真正规定什么时候使用一个,为什么,所以让我添加一些东西。
无主或弱的讨论归结为变量的生命期问题和引用它的闭包。
方案
你可以有两种可能的情况:
-
闭包拥有与变量相同的生命周期,所以闭包只有在变量可到时才可以访问 。 变量和闭包有相同的生命周期。 在这种情况下,您应该声明该参考为无主 。 一个常见的例子是许多小封闭例子中使用的
[unowned self]
,他们在父母的背景下做了一些事情,而在其他任何地方都没有被引用,他们的父母没有活过。 -
闭包的生命周期与变量之一无关,闭包仍然可以在变量不可达时被引用。 在这种情况下,您应该声明引用为弱,并在使用之前验证它不是零(不要强制展开)。 一个常见的例子就是你可以在一些关于引用一个完全不相关的(生命周期)的委托对象的例子中看到
[weak delegate]
。
实际使用
那么,你将会/应该实际使用大部分时间吗?
引用Joe Groff的推特 :
无主是更快,并允许不变性和非选择性。
如果你不需要弱点,不要使用它。
你会在这里找到更多关于无主*
内部工作。
*
通常也称为无主(安全),表示在访问无主引用之前执行运行时检查(导致无效引用崩溃)。
如果自我可能在封闭使用[弱自我] 。
如果封闭使用[无主的自我], 自我永远不会成为零。
Apple Swift文档有一个很大的部分,用图像来解释使用强 , 弱和无主闭包的区别:
这里是来自苹果开发者论坛的精彩报价描述了美味的细节:
unowned
与unowned(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) }