swift进阶七:引用计数(Strong、Unowned、Wea
上一节,介绍了方法调度 & @objc & 指针。本节,我们就探究较难的引用计数
,将从以下4个方面探索:
- Swift三大引用计数(strong、unowned、weak)
- 强引用 & 无主引用
- CFGetRetainCount计数统计
- 弱引用
swift
中的引用计数
与OC一致
,都是采用ARC
(自动引用计数)管理。
- OC引用计数:(可参考OC内存管理 -> 3.引用计数)
OC的对象都是以
objc_object
为模板创建,其中首元素
是isa
:
开启
指针优化(nonpointer): 在isa
中存储引用计数
,可使用散列表
进行拓展
存储未开启
指针优化: 直接使用散列表
进行存储
。
- swift引用计数:
swift对象
都是以HeapObject
为模板创建,其中HeapObject
的模板中第二个元素
,是refCount
引用计数属性,该属性记录了strong
(强引用计数)和unowned
(弱引用计数)等信息。weak修饰
的对象
,会另外生成WeakReference
对象,内部HeapObjectSideTableEntry
散列表类在原heapObject类
的基础上,重新记录了refCount
(管理strong
和unowned
引用计数)并新增了weakBits
弱引用计数。
1. Swift三大引用计数(strong、unowned、weak)
首先,我们先通过案例,体验一下Swift
对象的三种引用类型:
-
strong
(默认强引用类型)、unowned
(无主引用类型)、weak
(弱引用类型)
image.png
- 不管是哪种
引用
,持有
的都是原对象
(从p到p5内存地址可以看出)- 在每一行执行完后,
x/4g
打印p
对象内存信息
,在第二
个地址
上,可以清晰感受到,强引用
和无主引用
的引用计数
在有规律
的增加
,而弱引用
却没有变化
。
- 经过了上面的初体验,我们对
强引用
和无主引用
计数的位置
有了初步的感受,但弱引用
的信息存放不明朗
。
- 下面,我们通过
案例
、SIL中间代码
、Swift源码
、汇编
等方式,一点点揭开
他们的面纱
😃
2. 强引用 & 无主引用
2.1 源码探索
- 当前以
默认
的initialized
方式进行初始化
,分析HeapObject对象
的引用计数
swift源码探索
过程:
image.png
-
refCount
的内存布局
:
image.png
- 现在,我们知道
强引用
和无主引用
是在Uint64_t
8位
的refCount
的不同位置。
2.2 引用计数分析
下面通过案例
来检查
一下:
- 创建一个
Swift
的命令行项目
:
class HTPerson {
var age = 10
var name = "ht"
}
var t = HTPerson()
var t1 = t
var t2 = t
print("end")
- 【尝试一】: 在
t1处
打断点
。t对象
的强引用
和无主引用
的计数
都为1
image.png
- 【尝试二】: 在
t2处
打断点
。t对象
的强引用计数
为2
和无主引用计数
为1
image.png
2.3 强引用计数+1
- 还是以上面
测试代码
为例,我们结合SIL
中间代码和Swift源码
分析:
【情况一】仅
image.png创建对象
,默认强引用计数
为1
【情况二】进行
一次引用
,强引用计数
为2
,SIL
中可以看到copy_addr
,汇编
可以看到使用swift_retain
,在swift源码
中可以知道执行路径为:image.png
swift_retain
->refCounts.increment(1)
->incrementStrongExtraRefCount
->强引用计数+1
3. CFGetRetainCount计数统计
-
CFGetRetainCount
会在执行前
,对对象
进行strong_retain
操作,在执行后
,完成release_value
操作。
所以swift
中CFGetRetainCount
打印的强引用计数
,会
比原引用计数多1
。
注意:swift中
,在lldb
中p打印
内存,会引用计数+1
,影响影响CFGetRetainCount
的结果
(断点,p打印一次或多次,x/4g
在内存信息中可看到引用计数
明显变化
)
【情况一】
image.png不打印
,无retain和release【情况二】
image.png打印
一次CFGetRetainCount
,执行前strong_retain
+1,执行完release_value
-1
4. 弱引用
- 我们知道
swift
是使用ARC
(自动引用计数管理)的。如果产生循环引用
,我们必须有弱引用
机制去打破循环
。
swift中的
弱引用
,使用weak修饰
。与OC不同的是:
OC
:
弱引用计数
是存放在全局维护
的散列表
中,isa
中会记录
是否使用了散列表
。
在引用计数
为0
时,自动触发dealloc
,会检查
并清空
当前对象
的散列表计数
。
swift
:
弱引用计数
也是存放在散列表
中,但这个散列表
不是全局的。
- 如果对象
没有
使用weak
弱引用,就是单纯的HeapObject
对象,没有散列表
。- 如果使用
weak
弱引用,会变为WeakReference
对象。这是一个Optionl(可空对象)
。其结构中自带散列表计数
区域。
但swift
的散列表
与refCount
无关联。当强引用计数
为0
时,不会触发散列表
的清空。而是在下次访问
发现当前对象不存在(为nil)
时,会清空散列表计数
。
下面,我们通过案例
和源码
来分析swift
的弱引用
: WeakReference对象
和内存结构
案例:
image.png
可以发现:
image.png
weak修饰前
,p对象是HeapObject类型
,可从refCount
中看出强引用计数
和无主引用计数
。
weak修饰后
,p对象的类型变了
可以看到
image.pngweak修饰
的p1对象
,变成了optinal可选值
。
(不难理解,weak修饰
的对象
,不
会改变
原对象的引用计数
,只是多
一层可空
的状态
)
image.png
断点
,汇编
可以看到swift_weakInit
初始化,swift_weakDestroy
释放。
进入
swift源码
,搜索swift_weakInit
:image.png
常规对象
与弱引用对象
区别:
- 现在,我们已知道
弱引用
实际上是WeakReference
对象,信息
都存储在side
弱引用表中,可仿照getSideTable
函数左移3位
得到side散列表地址
。读取弱引用信息
:我们回到
image.png上面案例
:
- 了解结构后,关于
弱引用
的引用计数+1
、-1
、释放
都在WeakReference
类中有介绍,可以自行了解。