弱引用和无主引用有什么区别?
弱引用和无主引用有什么区别?
Swift有:
- 强引用(Strong References)
- 弱引用(Weak References)
- 无主引用(Unowned References)
无主引用和弱引用有什么不同?
在什么时候使用无主引用是安全的?
无主引用是否像C/C++中的悬垂指针(dangling pointers)一样存在安全风险?
Q1. “Unowned reference”和“Weak Reference”有什么区别?
弱引用:
弱引用是一种引用,它不会对它所引用的实例保持强持有,所以不会阻止ARC处理被引用的实例。由于弱引用可以“没有值”,所以必须将每个弱引用声明为可选类型。(Apple Docs)
无主引用:
与弱引用类似,无主引用不会对其引用的实例保持强持有。但与弱引用不同的是,无主引用被认为总是有值的。因此,无主引用始终被定义为非可选类型。(Apple Docs)
何时使用:
在某个引用在其生命周期中的某个时间点上可以成为nil时,使用弱引用。相反,当您知道引用在初始化后永远不会为nil时,请使用无主引用。(Apple Docs)
Q2. 何时使用“无主引用”是安全的?
如上所述,无主引用被认为总是有值的。因此,只有在您确定引用永远不为nil时才应使用它。Apple Docs通过以下示例说明了无主引用的用例。
假设我们有两个类Customer
和CreditCard
。客户可以没有信用卡,但是信用卡将不会没有客户,即可以假设信用卡始终有一个客户。因此,它们应该具有以下关系:
class Customer { var card: CreditCard? } class CreditCard { unowned let customer: Customer }
Q3. “无主引用”像C / C ++中的“悬垂指针”一样会有安全风险吗?
我认为不会。
由于无主引用只是保证有值的弱引用,因此它不应以任何方式构成安全隐患。但是,如果在所引用实例被销毁后尝试访问无主引用,则会触发运行时错误,应用程序将崩溃。
这是我看到的唯一风险。
weak
和 unowned
引用都不会增加被引用对象的引用计数(也就是不会创建强引用),以此避免自动引用计数(ARC)过早释放被引用对象。
为什么需要两个关键字呢?这是因为 Optional
类型是 Swift 语言内置的。简单来说:可选类型 提供了内存安全性,这与 Swift 的构造器规则 完全契合,而这些规则即为了提供这种好处而严格执行。
weak
引用可能会变为 nil
(当被引用对象被释放时就会变为 nil
),因此你的属性类型必须是可选类型,作为程序员,你需要在使用前强制检测是否为空(基本上编译器会尽最大努力让你编写安全的代码)。
unowned
引用假设在其生命周期内永远不会变为 nil
。 unowned
引用必须在初始化时设置,这意味着引用将被定义为非可选类型,可以安全地使用而无需进行检查。如果某个被引用的对象被释放了,那么当使用该 unowned
引用时,应用将会崩溃。
来自 Apple 文档 的描述:
当引用在其生命周期内可能会变为 nil 时,请使用弱引用。当您知道一旦在初始化之后引用就永远不会为 nil 时,请使用无主引用。
在文档中,还有一些讨论保留循环及如何破解它们的例子。所有这些例子都是从 文档 中提取的。
weak
关键字的例子:
class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? } class Apartment { let number: Int init(number: Int) { self.number = number } weak var tenant: Person? }
现在,来看一些 ASCII 艺术(你应该去看一下 文档 - 它们有非常棒的图表):
Person ===(strong)==> Apartment Person <==(weak)===== Apartment
Person
和 Apartment
的例子展示了这样一种情况:两个属性都允许为 nil
,同时也有可能导致强引用循环。此时,最好使用弱引用。这两个实体都可以存在而不必直接依赖于对方。
unowned
关键字的示例:
class Customer { let name: String var card: CreditCard? init(name: String) { self.name = name } } class CreditCard { let number: UInt64 unowned let customer: Customer init(number: UInt64, customer: Customer) { self.number = number; self.customer = customer } }
在这个例子中,一个Customer
可能有也可能没有CreditCard
,但CreditCard
总是与Customer
相关联。为了表示这一点,Customer
类有一个可选的card
属性,但CreditCard
类有一个非可选的(并且是未拥有的)customer
属性。
Customer ===(strong)==> CreditCard Customer <==(unowned)== CreditCard
Customer
和CreditCard
示例展示了这样的情况:一个允许为nil,而另一个不能为nil的属性可能会导致强引用循环。这种情况最好使用未拥有的引用来解决。
Apple的注释:
弱引用必须声明为变量,以表明它们的值在运行时可以更改。不能把弱引用声明为常量。
还有第三种情况,当两个属性应该始终拥有值,并且一旦初始化完成,任何一个属性都不应为空时。
还有在处理闭包时要避免的典型保留周期情况。