swift内存管理探索

2022-01-18  本文已影响0人  余晖依旧耀眼

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<refcountIsInline, sizeof(void*)>::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<InlineRefCountBits> {
        strong RC + unowned RC + flags
        OR
        HeapObjectSideTableEntry*
      }
    }
  }
//有弱引用情况
  HeapObjectSideTableEntry {
    SideTableRefCounts {
      object pointer
      atomic<SideTableRefCountBits> {
        strong RC + unowned RC + weak RC + flags
      }
    }   
  }

InlineRefCountsSideTableRefCounts公用模板类RefCounts<T>的实现
InlineRefCountBitsSideTableRefCountBits公用模板RefCountBitsT<bool>

4、无主引用

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


崩溃.png

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

总结:

上一篇 下一篇

猜你喜欢

热点阅读