Swift(十七)-弱引用与无主引用

2,101 阅读4分钟

「这是我参与2022首次更文挑战的第25天,活动详情查看:2022首次更文挑战

弱引用

Swift中我们通过关键字weak来表明一个弱引用;weak关键字的作用是在使用这个实例的时候并不保有此实例的引用。使用weak关键字修饰的引用类型数据在传递时不会使引用计数加1,不会对其引用的实例保持强引用,因此不会阻止ARC释放被引用的实例。

由于弱引用不会保持对实例的引用,所以当实例被释放的时候,弱引用仍旧引用着这个实例也是有可能。因此,ARC会在被引用的实例释放时,自动地将弱引用设置为nil。由于弱引用需要允许设置为nil,因此它一定是可选类型

我们将上述代码进行修改,为ClassB中的ClassA属性添加weak关键字:

image.png

可以看到,内存被正确的释放;

那么weak纠结对我们的代码做了什么处理呢?

我们定义如下代码:

weak var obj3 = ClassA()

打上断点,当运行至此代码时,我们查看汇编代码会发现调用了swift_weakInit

image.png

我们从Swift源码中找到swift_weakInit的实现:

image.png

从源码中可以看到,声明一个weak关键字实质上是定义了一个WeakReference对象;那么nativeInit方法做了什么事情呢?

image.png

我们看到,在nativeInit方法中调用了formWeakReference()方法,也就意味着形成了弱引用(形成一个散列表):

image.png

可以看到在formWeakReference中其实是创建了一个散列表,其创建如下:

image.png

  • 取出原有的64位信息,也就是refCounts
    • 如果存在引用计数,则返回引用计数;
    • 如果不存在引用计数或者正在被析构,则返回null;
  • 之后会创建散列表HeapObjectSideTableEntry

根据如下源码:

image.png

可以分析出在Swift中本质上存在两种引用计数:

  • InlineRefCounts里边存储strong RCunowned RCflags标志位;如果当前存在引用计数,那么将会存储HeapObjectSideTableEntry
    • 如果是强引用,那么是strong RC + unowned RC + flags
    • 如果是弱引用,那么是 HeapObjectSideTableEntry
  • HeapObjectSideTableEntry里边存储的是64位原有的strong RC + unowned RC + flags,再加上32位weak RC
  • InlineRefCountsSideTableRefCounts都是公用的RefCountBitsT

我们可以通过代码验证一下,查看HeapObjectSideTableEntry的实现:

image.png

可以看到其refCountsSideTableRefCounts类型的:

image.png

而最终都指向了RefCountBitsT

image.png

SideTableRefCounts在继承RefCountBitsT的同时也会继承其64位的位域信息,同时又多出了32位weakBits

我们通过代码来看一下:

image.png

通过代码发现,被weak修饰之后,其内存数据发生了变化,我们查看十六进制发现:

image.png

首先使用weak之后其6263位被标记为了1,我们将其还原为0

image.png

我们在分析引用计数的时候,强引用是通过左移计数的,那么弱引用应该也是类似的原理,我们在源码中能够找到散列表的偏移代码:

image.png

static const size_t SideTableUnusedLowBits = 3;

也就是在源码中,将我们的散列表的内存地址右移了3位,那么我们将地址0x20E80810左移3位就能找到散列表地址:

image.png

我们分析其内存数据如下:

image.png

我们已经知道了弱引用只能修饰可选类型,其引用的实例被释放后,属性会被自动置为nil;那么问题来了,如果我们使用的属性非可选类型,又恰好出现了循环引用,那么应该如何处理呢?在Swift中为我们提供了另外一个关键字unowned(无主引用)

无主引用

无主引用弱引用最大的区别在于,无主引用总是假定属性是不为nil的,如果属性所引用的实例被销毁释放了,再次使用这个实例程序就会崩溃。而弱引用则允许属性值为nil,因此不会崩溃。两者相比,弱引用更加兼容,无主引用不太安全;

我们将上述代码中的weak修改为unowned

image.png

那么对于p1此时其被声明为了无主引用

那么针对弱引用无主引用如何选择呢?

  • 如果强引用的双方生命周期没有任何关系,使用weak,如delegate
  • 如果其中一个对象销毁,另一个对象也跟着销毁,则使用unowned

weak相对于unowned更兼容,更安全,而unowned性能更高;这是因为weak需要操作散列表,而unowned只需要操作64位位域信息;在使用unowned的时候,要确保其修饰的属性一定有值;