Swift中解决引用循环之Unowned 与 Weak的选择
Swift的内存管理机制与Object-C一样,都是采用了自动内存管理 -- ARC。那么这样就不得不想到老生常谈的一个问题——引用循环。
Object-C中,我们习惯使用__weak关键词来打破这样的循环,使循环引用的对象得到释放。而在Swift中,有两种方式可以做到同样的效果,它们分别是Unowned
与 Weak
关键词。尽管两者确实都能解决引用循环的问题,但是两者的使用场景稍有不同。
因为目的是解决引用循环的问题,那就block(闭包)的函数体中取消引用循环来讨论。
1.Unowned
和Weak
的使用时机
Unowned
一般使用在其所修饰的对象和所处的block环境的生命周期一致的时候。
简单来说,Unowned
修饰的对象,在整个block的使用期间都应该是有效的,即不可为nil。
Weak
则可以使用为block 的生命周期超出其对象进行修饰。
意思是可以修饰可能变成nil的对象。用Swift中的话语来说就是Optional对象。
相比之下,weak
的使用范围更加广泛,如果不考虑性能的话,我们大可以无论什么情况都使用weak
将会更加安全。然而,既然Unowned
存在,必将有他的意义 —— 出于性能考虑,我们应该在可以使用Unowned
的时候尽可能的使用Unowned
,具体原因请往下看。
2.Unowned
比Weak
的开销更小
Swift 中的每个对象保持了两个引用计数器,一个是强引用计数器,用来决定 ARC 什么时候可以安全地析构这个对象,另外一个附加的弱引用计数器,用来计算创建了多少个指向这个对象的 unowned 或者 weak 引用,当这个计数器为零时,这个对象将被析构 。
弱引用计数器在增加之前(这是一个原子操作),会事先检查一个对象的强引用计数器的值,当值大于0的时候,才确保这是有效的。不然我们访问一个无效的值将会引起程序运行时报错从而导致崩溃。
Unowned
在编译的时候,进行了优化,它不会对有效只进行辨别:如果其引用的对象是一个有效值,它将会顺利在弱引用计数器原本的基础之上加1。如果是一个无效的值,那它将指向一个垃圾内存中的地址,在运行的时候,这将会产生错误,这就是为什么我们需要在使用Unowned
时候,要保证对象生命周期的有效性。
Weak
针对Unowned
进行了包装(光凭这一点,Unowned
在性能上优于Weak
)。Swift 的 weak 引用添加了附加层,间接地把 unowned 引用包裹到了一个可选容器里面,会对可选值进行相应的处理,这将带来一定的开销。所以尽管我们给一个nil对象添加Weak
修饰,程序运行中,依旧不会报错。
这里的性能对比,验证了上面所说的,我们应该在对象的生命周期允许的情况下,尽量使用Unowned
。