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_
方法
找到我们对象的结构体
HeapObject
对象的结构体.png
找到
refcount
的定义
refcounts.png
InlineRefCounts.png
RefCounts.png
上一步调用RefCounts
模板传入的是InlineRefCountBits
RefCountBitsT.png
RefCountBitsT
是一个模板类,传入的是RefCountIsInline
,这个模板类只有一个BitsType
类型的参数bits。找到BitsType
的定义typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type BitsType;
,点击进去RefCountBitsInt
最终可以看到引用计数本质是一个uint64_t
类型的信息。再回到第一步对象的结构体HeapObject
中的RefCount
类型
RefCounts初始化.png
从上面找源码的过程中,可以知道RefCountBits
其实就是RefCountBitsT
,找到初始化方法
strongExtraCount
传入的是0
unownedCount
传入的是1
计算StrongExtraRefCountShift
StrongExtraRefCountShift = shiftAfterField(IsDeiniting) = IsDeinitingShift + IsDeinitingBitCount = UnownedRefCountShift + UnownedRefCountBitCount + IsDeinitingBitCount = PureSwiftDeallocShift + PureSwiftDeallocBitCount + UnownedRefCountBitCount + IsDeinitingBitCount
StrongExtraRefCountShift计算.png
所以
StrongExtraRefCountShift
= 0 + 1 + 31 + 1 = 33PureSwiftDeallocShift
= 0UnownedRefCountShift
= 1
0 << 33 = 0
1 << 0 = 1
1 << 1 = 2
所以最终的计算结果是(0|1|2) = 3
这就对应上了2.1当中打印结果
打印结果.png
2.3、对象赋值操作
下面来看看赋值操作,引用计数会有啥变化
赋值操作.png
从sil代码看看赋值的底层操作
通过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、源码探索
代码.png3.2.1、汇编代码
汇编.png调用了swift_weakInit
方法
3.2.2、源码流程
swift_weakInit.pngnativeInit.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位设置标记位置
SideTableRefCountBits.png
SideTable 是HeapObjectSideTableEntry
类型,也有refCounts
,内部是SideTableRefCountBits
,就是在原来的uint64_t
加上一个uint32_t
3.2.3、代码输出
弱引用.png弱引用后的引用计数是0xc000000020c010da,我们将引用计数还原成HeapObjectSideTableEntry
的side(上面的计算流程反过来:去62,63位去1,然后再左移3位)
通过计算器计算得到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
}
}
}
InlineRefCounts
和SideTableRefCounts
公用模板类RefCounts<T>
的实现
InlineRefCountBits
和SideTableRefCountBits
公用模板RefCountBitsT<bool>
4、无主引用
和弱引用类似,无主引用不会对实例强持有。不同于弱引用的是,无主引用是假定永远有值的。
崩溃.png
unowned
是假定永远有值的,当前p是nil,所以会崩溃。在使用unowned
的时候需要慎用
总结:
- 如果两个对象的生命周期和对方完全没有关系(其中一方无论何时置为nil,都不会影响对象),请用weak
- 如果确保一个对象销毁,另一个对象也会跟着销毁,此时就可以用unowned
网友评论