swift内存管理探索

1、概念

swift中使用自动引用计数(ARC)机制来追踪和管理内存

2、强引用

当前环境:Xcode 13.1,swift源码5.5.2
默认创建的对象,都是强引用的,这点跟oc是一样的。

2.1、打印对象信息

class YYPeople{
    var age :Int = 18
    var name :String = "lisi"
    var height :Double = 175.0
}
//打印指针信息
//注意此处不能使用po p,po会增加p的引用计数
print(Unmanaged.passUnretained(p as AnyObject).toOpaque())
var p = YYPeople()
print("end")
输出信息.png

po会使p的引用计数+1,但是看po前后的变化0x0000000000000003->0x0000000200000003->0x0000000400000003,通过计算器查看三个数据,可以看出p的引用计数+1后,引用计数信息第33位变成了1;再+1后,引用计数信息第34位变成了1


计算器查看数据.png

可以看到的是引用计数的变化,并不是直接+1,而是refercount存储的信息发生变化。

2.2、通过源码探索

首先在HeapObject.cpp找到实例对象创建_swift_allocObject_方法

_swift_allocObject_.png

找到我们对象的结构体HeapObject

对象的结构体.png

找到refcount的定义

refcounts.png

InlineRefCounts.png

RefCounts.png

上一步调用RefCounts模板传入的是InlineRefCountBits

InlineRefCountBits.png

RefCountBitsT.png

RefCountBitsT是一个模板类,传入的是RefCountIsInline,这个模板类只有一个BitsType类型的参数bits。找到BitsType的定义typedef typename RefCountBitsInt::Type BitsType;,点击进去RefCountBitsInt

RefCountBitsInt.png

最终可以看到引用计数本质是一个uint64_t类型的信息。再回到第一步对象的结构体HeapObject中的RefCount类型

RefCount初始化.png

RefCounts初始化.png

从上面找源码的过程中,可以知道RefCountBits其实就是RefCountBitsT,找到初始化方法

RefCountBitsT初始化.png

strongExtraCount传入的是0
unownedCount传入的是1

计算StrongExtraRefCountShift

StrongExtraRefCountShift.png

StrongExtraRefCountShift = shiftAfterField(IsDeiniting) = IsDeinitingShift + IsDeinitingBitCount = UnownedRefCountShift + UnownedRefCountBitCount + IsDeinitingBitCount = PureSwiftDeallocShift + PureSwiftDeallocBitCount + UnownedRefCountBitCount + IsDeinitingBitCount
StrongExtraRefCountShift计算.png

所以StrongExtraRefCountShift = 0 + 1 + 31 + 1 = 33
PureSwiftDeallocShift = 0
UnownedRefCountShift = 1

0 << 33 = 0
1 << 0 = 1
1 << 1 = 2
所以最终的计算结果是(0|1|2) = 3
这就对应上了2.1当中打印结果


打印结果.png

2.3、对象赋值操作

下面来看看赋值操作,引用计数会有啥变化


赋值操作.png

从sil代码看看赋值的底层操作

赋值操作.png

通过sil文档查看copy_addr的作用
copy_addr.png

继续降级成IR代码
IR代码.png

可以看出来p赋值给p1就是调用swift_retain方法,从源码查看
swift_retain.png

incrementNonAtomic.png

incrementStrongExtraRefCount.png

所以赋值操作最终是引用计数增加1<<33 ,也就是RefCount的高33位+1
完美对应上了我们输出的RefCount变化

3、弱引用

3.1、作用

弱引用不会对其引用的对象保持强引用,因此不会阻止ARC释放被引用的实例对象,这个特性可以阻止循环引用的产生。声明的属性或者变量前面加上weak关键字表明是弱引用。
swift中弱引用必须是可选类型,因为引用的实例被释放后,ARC会自动将其置为nil。

3.2、源码探索

代码.png

3.2.1、汇编代码

汇编.png

调用了swift_weakInit方法

3.2.2、源码流程

swift_weakInit.png

nativeInit.png

formWeakReference.png.png

allocateSideTable.png

initRefCounts.png

常量值.png

计算

SideTableUnusedLowBits :3
UseSlowRCShift = shiftAfterField(StrongExtraRefCount) = StrongExtraRefCountShift + StrongExtraRefCountBitCount =
33 + 30 = 63
SideTableMarkShift = SideTableBitCount = 62
可以看出也是将side右移3位存储到64位的信息当中,并在63位和62位设置标记位置

HeapObjectSideTableEntry.png

SideTableRefCountBits.png

SideTable 是HeapObjectSideTableEntry类型,也有refCounts,内部是SideTableRefCountBits,就是在原来的uint64_t加上一个uint32_t

3.2.3、代码输出

弱引用.png

弱引用后的引用计数是0xc000000020c010da,我们将引用计数还原成HeapObjectSideTableEntry的side(上面的计算流程反过来:去62,63位去1,然后再左移3位)

计算器还原side.png

通过计算器计算得到sideTable的地址,然后再读取其中信息


代码验证side.png

3.2.4 引用计数总结

一个实例对象在首次初始化的时候,是没有sideTable的,当我们创建一个弱引用的时候,才会创建sideTable
对于HeapObject来说就会存在两种情况的引用计数的布局方式

//没有弱引用情况
  HeapObject {
    isa
    InlineRefCounts {
      atomic {
        strong RC + unowned RC + flags
        OR
        HeapObjectSideTableEntry*
      }
    }
  }
//有弱引用情况
  HeapObjectSideTableEntry {
    SideTableRefCounts {
      object pointer
      atomic {
        strong RC + unowned RC + weak RC + flags
      }
    }   
  }

InlineRefCountsSideTableRefCounts公用模板类RefCounts的实现
InlineRefCountBitsSideTableRefCountBits公用模板RefCountBitsT

4、无主引用

和弱引用类似,无主引用不会对实例强持有。不同于弱引用的是,无主引用是假定永远有值的。


崩溃.png

unowned是假定永远有值的,当前p是nil,所以会崩溃。在使用unowned的时候需要慎用

总结:

  • 如果两个对象的生命周期和对方完全没有关系(其中一方无论何时置为nil,都不会影响对象),请用weak
  • 如果确保一个对象销毁,另一个对象也会跟着销毁,此时就可以用unowned

你可能感兴趣的:(swift内存管理探索)