美文网首页
swift内存管理探索

swift内存管理探索

作者: 余晖依旧耀眼 | 来源:发表于2022-01-18 16:00 被阅读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的时候需要慎用

    总结:

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

    相关文章

      网友评论

          本文标题:swift内存管理探索

          本文链接:https://www.haomeiwen.com/subject/umhchrtx.html