Swift进阶-内存管理

本文的主要目的是探索 RefCount 的内存结构及强/弱引用计数管理

Swift 中也是采用 ARC 编译器自动内存管理机制。
Swift 对象的内存结构是 HeapObject, 有两个属性 Metadata 和 RefCount , 各占8字节(64位)。
RefCount 的每位的数据存储内容如下图所示:
Swift进阶-内存管理
文章图片


1. 强引用的引用计数 1.1 数据结构 Swift进阶-内存管理
文章图片


数据结构体大概是这样:

struct InlineRefCountBits { var strongref: UInt32 var unownedRef: UInt32 }


1.2 存储方式 源码中定义了 64 位的 refCount 联合体的不同位置,存储的不同含义:
Swift进阶-内存管理
文章图片


Swift进阶-内存管理
文章图片


我们通过下面这个小案例,来看看强引用的引用计数的存储方式:
Swift进阶-内存管理
文章图片


我们把这个值放到计算器中,对比着看下 64 位的二进制存储情况:
Swift进阶-内存管理
文章图片


从33 - 62位存储的是强引用数量,二进制的 11, 表示的是3,也就是有3个强引用计数。强引用计数是从0开始的,有t, t1, t2 三个变量指向创建的对象,所以对象的强引用计数为3。
那控制台里打印的0x0000000600000002中的6是怎么回事呢?
  • 由于我们在控制台打印的是16进制,每4位为一组,第32 - 36位的 0110, 表示的是数字6,所以才会显示成0x0000000600000002,这里只是进制计算方式不同,并不是代表有6个引用计数。
  • 由于二进制是从第33位开始存储的值是11,16进制却从32位开始存储的值是110。110 比 11向左移1位,所以就形成了2倍关系。所以我们从控制台打印出来的值,除以2,就是真实的强引用计数了。
1.3 作用 在ARC下,编译器会根据强引用数量,来判断一个对象是否应该被销毁。如果强引用为0,就会销毁这个对象。
如果两个对象,互相强持有对方,则会造成引用计数均不能为0,也就是无法销毁,从而造成内存泄露的问题。
这时就需要弱引用或者无主引用,来将一个持有方式改为非强引用。这样就可以解除循环引用的问题。

下面就是一个循环引用的例子:
class Teacher{ var age = 18 var closure:(()->())?deinit { print("Teacher 销毁了") } }func test() { var t = Teacher() t.closure = { t.age = 10 } print("方法执行完了") }test()


Swift进阶-内存管理
文章图片


由于对象 t 持有closure,closure 内又捕获了 t 的变量,从而强持有了 t . 所以造成了循环引用。
2.弱引用 使用weak 来修饰变量,这个变量就是弱引用,强引用计数不会再加1。weak 修饰后的变量,会变成一个可选项,也就是可以将 nil 赋值给它。
2.1 数据结构 数据结构:
Swift进阶-内存管理
文章图片


调用流程:
Swift进阶-内存管理
文章图片


所以WeakReference大概是这样的结构:
struct WeakReference { var entry: HeapObjectSideTableEntry }struct HeapObjectSideTableEntry { var object: HeapObject var refCounts: SideTableRefCounts }struct SideTableRefCounts { var strongref: UInt32 var unownedRef: UInt32 var weakBits: UInt32 }struct HeapObject { var kind: UnsafeRawPointer var strongref: UInt32 var unownedRef: UInt32 }


2.2 存储方式 Swift进阶-内存管理
文章图片


下面是个例子:
Swift进阶-内存管理
文章图片


Swift进阶-内存管理
文章图片


我们创建了4个弱引用的变量,弱引用计数从1开始计数,所以是5.
2.3 作用 weak 修饰的类型会自动变为可选类型,可以用于解除循环引用,在对象销毁后,编译器会自动置为nil。
1.3 中的例子,在捕获 t 的时候,使用 weak 修饰一下,就不会形成闭包对 t 对象的强引用。
func test() { var t = Teacher() t.closure = {[weak t] in t?.age = 10 } print("方法执行完了") }


Swift进阶-内存管理
文章图片


3 无主引用 存储方式及数据结构在强引用中,已经分析过了,下面这个图是结果:
Swift进阶-内存管理
文章图片


3.1 计数方式 Swift进阶-内存管理
文章图片


所以无主引用的计算方式为:
  1. 转换成 2 进制, 比如 6 转成 110
  2. 右移一位,110 变 10, 也就等于10进制的3
  1. 减 1,3 - 1 = 2
对象 t 的无主引用的值是 2

3.2 作用 由于 weak 修饰后的变量是可选项,使用时需要解包,比较麻烦。
如果能从上下文中确定变量的一定有值的话,可以使用 unowned 修饰变量来解除循环引用,因为它也不是强引用。这有点类似于隐式解包,在确定有值时再使用,如果对象释放后,再执行这个闭包,会崩溃的。
Swift进阶-内存管理
文章图片



4. 获取引用计数 4.1 CFGetRetainCount 需要注意的是, 如果我们使用CFGetRetainCount来获取强引用计数,这个方法的内部,会自动将当前的引用计数加1. 这一逻辑与 OC 是一致的:
Swift进阶-内存管理
文章图片

4.2 swift_retainCount 从源码看,swift_retainCount 获取引用计数的时候,也是默认 + 1 的。

Swift进阶-内存管理
文章图片
Swift进阶-内存管理
文章图片


5.引用计数加1 在ARC下,编译器为我们自动做了内存管理的操作,在有强引用 / 弱引用 / 无主引用指向对象时,都会执行对应的方法,去将对象对应的引用计数 + 1。

下面是swift_retain(强引用)的源码:
Swift进阶-内存管理
文章图片


6.引用计数减1 在ARC下,编译器为我们自动做了内存管理的操作,当指向对象的变量离开当前作用域时,根据对应变量的类型(强引用 / 弱引用 / 无主引用),执行对于的方法,将对象对应的引用计数 - 1。

下面是swift_release(强引用)的源码:
Swift进阶-内存管理
文章图片


好了 给小伙伴分解到这里就到此为止吧,以后会慢慢给大家讲解说明。
【Swift进阶-内存管理】青山不改,绿水长流,后会有期,感谢每一位佳人的支持!

    推荐阅读