Swift中weak和unowned有什么内部区别?

22
我了解Swift中weakunowned之间的使用和表面差异:
最简单的例子是,如果有一个Dog和一个Bone,则Bone可以对Dog进行弱引用(反之亦然),因为它们各自可以独立存在。
另一方面,在HumanHeart的情况下,Heart可以有一个对人类的unowned引用,因为一旦Human变得“dereferenced”,就无法合理地访问Heart。 这是与CustomerCreditCard的经典示例。
因此,这不是关于这个问题的重复。
我的问题是,拥有两个如此相似的概念的意义何在?内部区别是什么,需要为本质上几乎相同的事情拥有两个关键字?问题是为什么存在差异,而不是差异是什么。
考虑到我们可以像这样设置变量:weak var customer: Customer!unowned变量的优点是非可选的是无意义的。唯一实际的优点是,我们可以通过let使unowned引用保持常数。
此外,编译器可能会出于某种原因进行更有效的优化。是否正确?还是发生了其他事情,提供了保留两个关键字的有力论据(尽管根据Stack Overflow的流量,微小的区别显然令新手开发者和经验丰富的开发者感到困惑)?

我非常想听听那些曾经在Swift编译器(或其他编译器)上工作过的人的意见。


2
可能是什么是弱引用和无主引用的区别?的重复问题。 - Dalija Prasnikar
2
@DalijaPrasnikar 不同意。OP正在询问实现的差异;您链接的问题是关于概念(即使用)差异的。 - jlehr
注意:每个引用都有重要的性能影响,请注意以下这些参考链接:https://dev59.com/31MH5IYBdhLWcg3wtRcg - Epic Byte
2个回答

21
我的问题是,为什么会有两个如此相似的概念呢?它们之间有哪些内部差异需要使用两个关键字来表示,尽管它们本质上看起来几乎相同(99%)?
它们并不相似。它们是非常不同的。
- `weak` 是一个高度复杂的概念,是在 ARC 引入时引入的。它执行了一项近乎神奇的任务,允许你避免强引用循环(通过避免强引用)而无需担心当所引用的对象不存在时出现悬空指针崩溃——在 ARC 引入之前,这种情况经常发生。 - 另一方面,`unowned` 是非 ARC 的 `weak`(更具体地说,它与非 ARC 的 `assign` 相同)。它就是我们过去必须冒险使用的东西,是引入 ARC 之前导致很多崩溃的根源。它非常危险,因为如果所引用的对象不存在,你可能会得到一个悬空指针和崩溃。
两者之间的区别在于,`weak` 为了实现其奇迹,需要运行时插入编译器幕后的大量额外开销。`weak` 引用由运行时进行内存管理。特别是,运行时必须维护所有被标记为 `weak` 引用的引用的 “草稿本”,以便如果一个被弱引用的对象不存在了,运行时可以定位到该引用并将其替换为 `nil`,以防止悬空指针。
因此,在 Swift 中,`weak` 引用总是指向一个可选项(正是为了可以使用 `nil` 进行替换)。这是额外开销的另一个来源,因为使用一个可选项需要额外的工作,必须进行解包才能对其进行任何操作。出于这个原因,在适用的情况下始终优先选择 unowned。但是,只有在绝对安全的情况下才使用它!使用 unowned 就等于放弃自动内存管理和安全性。你故意回到了 ARC 之前不安全的时代。
在我的使用中,常见情况出现在需要一个捕获列表涉及 self 的闭包中,以避免保留循环。在这种情况下,几乎总是可以在捕获列表中使用 [unowned self]。使用 unowned 有以下几点好处:
  • 对程序员来说更方便,因为没有需要解包的内容。使用 [weak self] 则需要解包 Optional 才能使用。

  • 更有效率,部分原因是因为解包总是增加额外的间接层级,而部分原因是因为它减少了运行时临时列表需要跟踪的一个弱引用。


谢谢,这是目前为止最好的答案,实际上深入探讨了编译器背后发生的事情。您有没有关于“弱引用”计算密集度的参考资料? - ephemer
3
你只需想象一下,ARC在幕后进行所有这些簿记所需要付出的努力。这个过程在精彩的WWDC 2012视频406中有很好地描述,当时引入了ARC。每个人都应该观看这个视频,因为Swift ARC是Objective-C ARC(实际上,我们现在知道Chris Lattner创造ARC正是为了能够创建Swift)。 - matt
无论如何,我的回答的重点是你的问题基于错误的前提。weakunowned之间的区别可能在你看来只是一个词替换另一个词,但它们是完全不同的东西 - 实际上,它们是不同的种类。你可能想阅读我的在线书籍,在那里我讨论了合成属性的内存管理策略(这本质上与你所问的相同)http://www.apeth.com/iOSBook/ch12.html#_property_memory_management_policies — weakweakassignunowned - matt
对于那些没有提出问题的人来说,它们似乎是一个单词替换另一个单词的替代品。因此,您怀疑的“前提”就是问题本身... - ephemer
1
@ephemer 好的,我明白你的意思了! 我的问题在于问题开头是“我理解”,而我想,“不,你不理解”。 但现在我明白了,你是说:我知道规则是“仅当引用对象始终存在且对此对象的生命至关重要时使用unowned”。你想知道的是为什么这是规则(以及次要的是是否有情况需要积极使用unowned而不是weak,因为显然你总是可以使用weak)。 你是对的,这是一个完全合适的问题。(我希望我已经满意地解释了它。) - matt
“涉及到很多额外的运行开销”,所以你是说1纳秒就是很大的开销吗?你有一些测量数据来证明吗?根据我的经验,你可以在任何地方使用“weak”,它不会影响你的性能,特别是在一些UI应用程序中。 - Nikolai Ischuk

9
一个weak引用实际上被设置为nil,当引用对象释放时,你必须检查它。而一个unowned引用也被设置为nil,但你不需要强制进行检查。
你可以使用if letguard?等方式检查一个weak是否为nil,但检查一个unowned是没有意义的,因为你认为这是不可能的。如果你错了,程序会崩溃。
在实践中,我发现我从来不使用unowned。虽然使用weak会有微小的性能损失,但对我来说,额外的安全性是值得的。
我只会将unowned用于需要进行优化的特定代码中,而不是通用的应用程序代码。
你所寻找的"为什么存在"是因为Swift旨在能够编写系统代码(如操作系统内核),如果它们没有最基本的原语,就无法做到这一点。
注意:我之前在这个答案中说过unowned不会被设置为nil。这是错误的,一个裸的unowned会被设置为nil。一个unowned(unsafe)不会被设置为nil,可能会成为悬空指针。这是为高性能需求而设计的,通常不应出现在应用程序代码中。

你似乎已经达成了这个问题的普遍共识(即“weak”比“unowned”更常见),但我不确定这是否能回答这个问题。它似乎描述的是表面使用差异,而非两个关键字存在于何种内部原因的解释。不过,有关Swift也被设计用于系统编程的评论是很好的,谢谢。 - ephemer
重新审视你的回答,另一个关键点是你不能保证未拥有的引用会被设置为 nil(我猜通常不会,而是作为危险的悬空指针)。谢谢。 - ephemer
有一些unowned(unsafe)会变成悬空指针。我会更新答案。 - Lou Franco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接