上一节,介绍了方法调度 & @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_t8位的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源码分析:
【情况一】仅
创建对象,默认强引用计数为1
image.png
【情况二】进行
一次引用,强引用计数为2,SIL中可以看到copy_addr,汇编可以看到使用swift_retain,在swift源码中可以知道执行路径为:
swift_retain->refCounts.increment(1)->incrementStrongExtraRefCount->强引用计数+1
image.png
3. CFGetRetainCount计数统计
-
CFGetRetainCount会在执行前,对对象进行strong_retain操作,在执行后,完成release_value操作。
所以swift中CFGetRetainCount打印的强引用计数,会比原引用计数多1。
注意:swift中,在lldb中p打印内存,会引用计数+1,影响影响CFGetRetainCount的结果
(断点,p打印一次或多次,x/4g在内存信息中可看到引用计数明显变化)
【情况一】
不打印,无retain和releaseimage.png
【情况二】
打印一次CFGetRetainCount,执行前strong_retain+1,执行完release_value-1image.png
4. 弱引用
- 我们知道
swift是使用ARC(自动引用计数管理)的。如果产生循环引用,我们必须有弱引用机制去打破循环。
swift中的
弱引用,使用weak修饰。与OC不同的是:
OC:
弱引用计数是存放在全局维护的散列表中,isa中会记录是否使用了散列表。
在引用计数为0时,自动触发dealloc,会检查并清空当前对象的散列表计数。
swift:
弱引用计数也是存放在散列表中,但这个散列表不是全局的。
- 如果对象
没有使用weak弱引用,就是单纯的HeapObject对象,没有散列表。- 如果使用
weak弱引用,会变为WeakReference对象。这是一个Optionl(可空对象)。其结构中自带散列表计数区域。
但swift的散列表与refCount无关联。当强引用计数为0时,不会触发散列表的清空。而是在下次访问发现当前对象不存在(为nil)时,会清空散列表计数。
下面,我们通过案例和源码来分析swift的弱引用: WeakReference对象和内存结构
案例:
可以发现:
weak修饰前,p对象是HeapObject类型,可从refCount中看出强引用计数和无主引用计数。
weak修饰后,p对象的类型变了
image.png
可以看到
weak修饰的p1对象,变成了optinal可选值。
(不难理解,weak修饰的对象,不会改变原对象的引用计数,只是多一层可空的状态)
image.png
断点,汇编可以看到swift_weakInit初始化,swift_weakDestroy释放。
image.png
进入
swift源码,搜索swift_weakInit:image.png
常规对象与弱引用对象区别:image.png
- 现在,我们已知道
弱引用实际上是WeakReference对象,信息都存储在side弱引用表中,可仿照getSideTable函数左移3位得到side散列表地址。读取弱引用信息:我们回到
上面案例:
image.png
- 了解结构后,关于
弱引用的引用计数+1、-1、释放都在WeakReference类中有介绍,可以自行了解。

image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png












网友评论