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")
![](https://img.haomeiwen.com/i13733936/1da45a6e0479a308.png)
po会使p的引用计数+1,但是看po前后的变化0x0000000000000003->0x0000000200000003->0x0000000400000003,通过计算器查看三个数据,可以看出p的引用计数+1后,引用计数信息第33位变成了1;再+1后,引用计数信息第34位变成了1
![](https://img.haomeiwen.com/i13733936/160ff9e57fbc5506.png)
可以看到的是引用计数的变化,并不是直接+1,而是refercount存储的信息发生变化。
2.2、通过源码探索
首先在HeapObject.cpp
找到实例对象创建_swift_allocObject_
方法
![](https://img.haomeiwen.com/i13733936/a2fe5cd4f74730a7.png)
找到我们对象的结构体
HeapObject
![](https://img.haomeiwen.com/i13733936/10c17fcf5e8245c0.png)
找到
refcount
的定义
![](https://img.haomeiwen.com/i13733936/75565ca087c976d8.png)
![](https://img.haomeiwen.com/i13733936/9bd8aa7c1f9eee54.png)
![](https://img.haomeiwen.com/i13733936/20bdc7a787091d05.png)
上一步调用RefCounts
模板传入的是InlineRefCountBits
![](https://img.haomeiwen.com/i13733936/d2b1a6cbb330abe5.png)
![](https://img.haomeiwen.com/i13733936/a1881776b736d2b1.png)
RefCountBitsT
是一个模板类,传入的是RefCountIsInline
,这个模板类只有一个BitsType
类型的参数bits。找到BitsType
的定义typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type BitsType;
,点击进去RefCountBitsInt
![](https://img.haomeiwen.com/i13733936/a616aedac277dab3.png)
最终可以看到引用计数本质是一个uint64_t
类型的信息。再回到第一步对象的结构体HeapObject
中的RefCount
类型
![](https://img.haomeiwen.com/i13733936/ba1274f910feed21.png)
![](https://img.haomeiwen.com/i13733936/ce7b190b7f13b462.png)
从上面找源码的过程中,可以知道RefCountBits
其实就是RefCountBitsT
,找到初始化方法
![](https://img.haomeiwen.com/i13733936/a6a79c0f0b2c4fe1.png)
strongExtraCount
传入的是0
unownedCount
传入的是1
计算StrongExtraRefCountShift
![](https://img.haomeiwen.com/i13733936/d091e293c7710efa.png)
StrongExtraRefCountShift = shiftAfterField(IsDeiniting) = IsDeinitingShift + IsDeinitingBitCount = UnownedRefCountShift + UnownedRefCountBitCount + IsDeinitingBitCount = PureSwiftDeallocShift + PureSwiftDeallocBitCount + UnownedRefCountBitCount + IsDeinitingBitCount
![](https://img.haomeiwen.com/i13733936/fff916e0e978e876.png)
所以
StrongExtraRefCountShift
= 0 + 1 + 31 + 1 = 33PureSwiftDeallocShift
= 0UnownedRefCountShift
= 1
0 << 33 = 0
1 << 0 = 1
1 << 1 = 2
所以最终的计算结果是(0|1|2) = 3
这就对应上了2.1当中打印结果
![](https://img.haomeiwen.com/i13733936/35ae4a37cfb6222c.png)
2.3、对象赋值操作
下面来看看赋值操作,引用计数会有啥变化
![](https://img.haomeiwen.com/i13733936/2cba23b3c3af0811.png)
从sil代码看看赋值的底层操作
![](https://img.haomeiwen.com/i13733936/3580b41c4c0cd673.png)
通过sil文档查看
copy_addr
的作用![](https://img.haomeiwen.com/i13733936/25b771673a7faab3.png)
继续降级成IR代码
![](https://img.haomeiwen.com/i13733936/8285a1e75491fe86.png)
可以看出来p赋值给p1就是调用
swift_retain
方法,从源码查看![](https://img.haomeiwen.com/i13733936/aa9fc184f99db0c5.png)
![](https://img.haomeiwen.com/i13733936/c5cb347ceea49f68.png)
![](https://img.haomeiwen.com/i13733936/5805bd9a2838e931.png)
所以赋值操作最终是引用计数增加1<<33 ,也就是RefCount的高33位+1
完美对应上了我们输出的RefCount变化
3、弱引用
3.1、作用
弱引用不会对其引用的对象保持强引用,因此不会阻止ARC释放被引用的实例对象,这个特性可以阻止循环引用的产生。声明的属性或者变量前面加上weak
关键字表明是弱引用。
swift中弱引用必须是可选类型,因为引用的实例被释放后,ARC会自动将其置为nil。
3.2、源码探索
![](https://img.haomeiwen.com/i13733936/1a14e1beea1ea45f.png)
3.2.1、汇编代码
![](https://img.haomeiwen.com/i13733936/58f4f2b6fcbb97ef.png)
调用了swift_weakInit
方法
3.2.2、源码流程
![](https://img.haomeiwen.com/i13733936/dd4b66490de57922.png)
![](https://img.haomeiwen.com/i13733936/e8407bf68e00be49.png)
![](https://img.haomeiwen.com/i13733936/034b087ca5ec042d.png)
![](https://img.haomeiwen.com/i13733936/107f384d56b05ec4.png)
![](https://img.haomeiwen.com/i13733936/9f1c74b00857f74d.png)
![](https://img.haomeiwen.com/i13733936/34754b1a185719a4.png)
计算
SideTableUnusedLowBits
:3
UseSlowRCShift
= shiftAfterField(StrongExtraRefCount) = StrongExtraRefCountShift + StrongExtraRefCountBitCount =
33 + 30 = 63
SideTableMarkShift
= SideTableBitCount = 62
可以看出也是将side右移3位存储到64位的信息当中,并在63位和62位设置标记位置
![](https://img.haomeiwen.com/i13733936/dd95527ad3d4f30b.png)
![](https://img.haomeiwen.com/i13733936/a76917449ab3f30e.png)
SideTable 是HeapObjectSideTableEntry
类型,也有refCounts
,内部是SideTableRefCountBits
,就是在原来的uint64_t
加上一个uint32_t
3.2.3、代码输出
![](https://img.haomeiwen.com/i13733936/98a7b2d8a991587c.png)
弱引用后的引用计数是0xc000000020c010da,我们将引用计数还原成HeapObjectSideTableEntry
的side(上面的计算流程反过来:去62,63位去1,然后再左移3位)
![](https://img.haomeiwen.com/i13733936/e71db927efbd2b71.png)
通过计算器计算得到sideTable的地址,然后再读取其中信息
![](https://img.haomeiwen.com/i13733936/8d632816bf722197.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、无主引用
和弱引用类似,无主引用不会对实例强持有。不同于弱引用的是,无主引用是假定永远有值的。
![](https://img.haomeiwen.com/i13733936/455470ee4c9e2a69.png)
unowned
是假定永远有值的,当前p是nil,所以会崩溃。在使用unowned
的时候需要慎用
总结:
- 如果两个对象的生命周期和对方完全没有关系(其中一方无论何时置为nil,都不会影响对象),请用weak
- 如果确保一个对象销毁,另一个对象也会跟着销毁,此时就可以用unowned