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

Swift 中也是采用 ARC 编译器自动内存管理机制。

Swift 对象的内存结构是 HeapObject, 有两个属性 Metadata 和 RefCount , 各占8字节(64位)

RefCount 的每位的数据存储内容如下图所示:

1. 强引用的引用计数

1.1 数据结构

数据结构体大概是这样:

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

1.2 存储方式

源码中定义了 64 位的 refCount 联合体的不同位置,存储的不同含义:

我们通过下面这个小案例,来看看强引用的引用计数的存储方式:

我们把这个值放到计算器中,对比着看下 64 位的二进制存储情况:

33 - 62位存储的是强引用数量,二进制的 11, 表示的是3,也就是有3个强引用计数强引用计数是从0开始的,t, t1, t2 三个变量指向创建的对象,所以对象的强引用计数为3。

那控制台里打印的0x0000000600000002中的6是怎么回事呢?

  • 由于我们在控制台打印的是16进制,每4位为一组,第32 - 36位的 0110, 表示的是数字6,所以才会显示成0x0000000600000002,这里只是进制计算方式不同,并不是代表有6个引用计数。
  • 由于二进制是从第33位开始存储的值是11,16进制却从32位开始存储的值是110110 比 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()

由于对象 t 持有closureclosure 内又捕获了 t 的变量,从而强持有了 t . 所以造成了循环引用。

2.弱引用

使用weak 来修饰变量,这个变量就是弱引用,强引用计数不会再加1。weak 修饰后的变量,会变成一个可选项,也就是可以将 nil 赋值给它。

2.1 数据结构

数据结构:

调用流程:

所以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 存储方式

下面是个例子:

我们创建了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("方法执行完了")
}

3 无主引用

存储方式及数据结构在强引用中,已经分析过了,下面这个图是结果:

3.1 计数方式

所以无主引用的计算方式为: 

  1. 转换成 2 进制, 比如 6 转成 110
  2. 右移一位,110 变 10, 也就等于10进制的3
  1. 减 1,3 - 1 = 2

对象 t 的无主引用的值是 2

3.2 作用

由于 weak 修饰后的变量是可选项,使用时需要解包,比较麻烦。

如果能从上下文中确定变量的一定有值的话,可以使用 unowned 修饰变量来解除循环引用,因为它也不是强引用。这有点类似于隐式解包,在确定有值时再使用,如果对象释放后,再执行这个闭包,会崩溃的。

4. 获取引用计数

4.1 CFGetRetainCount

需要注意的是, 如果我们使用CFGetRetainCount来获取强引用计数,这个方法的内部,会自动将当前的引用计数加1. 这一逻辑与 OC 是一致的:

4.2 swift_retainCount

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

5.引用计数加1

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

下面是swift_retain(强引用)的源码:

6.引用计数减1

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

下面是swift_release(强引用)的源码:

好了 给小伙伴分解到这里就到此为止吧,以后会慢慢给大家讲解说明。

青山不改,绿水长流,后会有期,感谢每一位佳人的支持!

Swift进阶-内存管理的更多相关文章

  1. 《从零开始学Swift》学习笔记(Day 61)——Core Foundation框架之内存管理

    原创文章,欢迎转载.转载请注明:关东升的博客 在Swift原生数据类型.Foundation框架数据类型和Core Foundation框架数据类型之间转换过程中,虽然是大部分是可以零开销桥接,零开销 ...

  2. 《从零開始学Swift》学习笔记(Day 61)——Core Foundation框架之内存管理

    原创文章,欢迎转载. 转载请注明:关东升的博客 在Swift原生数据类型.Foundation框架数据类型和Core Foundation框架数据类型之间转换过程中,尽管是大部分是能够零开销桥接,零开 ...

  3. Swift中的可选链与内存管理(干货系列)

    干货之前:补充一下可选链(optional chain) class A { var p: B? } class B { var p: C? } class C { func cm() -> S ...

  4. Swift内存管理、weak和unowned以及两者区别

    Swift 是自动管理内存的,这也就是说,我们不再需要操心内存的申请和分配.当我们通过初始化创建一个对象时,Swift 会替我们管理和分配内存.而释放的原则遵循了自动引用计数 (ARC) 的规则:当一 ...

  5. Swift内存管理-示例讲解

    具体而言,Swift中的ARC内存管理是对引用类型的管理,即对类所创建的对象采用ARC管理.而对于值类型,如整型.浮点型.布尔型.字符串.元组.集合.枚举和结构体等,是由处理器自动管理的,程序员不需要 ...

  6. swift 内存管理,WEAK 和 UNOWNED

    因为 Playground 本身会持有所有声明在其中的东西,因此本节中的示例代码需要在 Xcode 项目环境中运行.在 Playground 中可能无法得到正确的结果. 不管在什么语言里,内存管理的内 ...

  7. Swift开发必备技巧:内存管理、weak和unowned

    因为 Playground 本身会持有所有声明在其中的东西,因此本节中的示例代码需要在 Xcode 项目环境中运行.在 Playground 中可能无法得到正确的结果. 不管在什么语言里,内存管理的内 ...

  8. 初步swift语言学习笔记6(ARC-自己主动引用计数,内存管理)

    笔者:fengsh998 原文地址:http://blog.csdn.net/fengsh998/article/details/31824179 转载请注明出处 假设认为文章对你有所帮助.请通过留言 ...

  9. Swift 内存管理详解

    Swift内存管理: Swift 和 OC 用的都是ARC的内存管理机制,它们通过 ARC 可以很好的管理对象的回收,大部分的时候,程序猿无需关心 Swift 对象的回收. 注意: 只有引用类型变量所 ...

随机推荐

  1. openFeign夺命连环9问,这谁受得了?

    1.前言 前面介绍了Spring Cloud 中的灵魂摆渡者Nacos,和它的前辈们相比不仅仅功能强大,而且部署非常简单. 今天介绍一款服务调用的组件:OpenFeign,同样是一款超越先辈(Ribb ...

  2. Jmeter集合点技术

    集合点简介 好比小学时候做广播体操,先让大家集合,等到时间统一开始做体操. 创建集合点 同步定时器 同时签到 注意:作用域 参数设置 用户数 为0 具体数值,不能大于 超时时间 为0,没有超时时间 具 ...

  3. Win10下部署VS+Qt+OpenCV+darknet(YOLO)集成环境

    VS+Qt集成环境 下载VS与Qt并安装. VS:官网链接 Qt:下载链接 将Qt的bin目录加入PATH路径 bin目录举例:D:\development\Qt\5.12.0\msvc2017_64 ...

  4. python测试开发工具库汇总(转载)

    Web UI测试自动化 splinter - web UI测试工具,基于selnium封装. selenium - web UI自动化测试. mechanize- Python中有状态的程序化Web浏 ...

  5. Vulnhub实战-JIS-CTF_VulnUpload靶机👻

    Vulnhub实战-JIS-CTF_VulnUpload靶机 下载地址:http://www.vulnhub.com/entry/jis-ctf-vulnupload,228/ 你可以从上面地址获取靶 ...

  6. C++默认参数静态绑定

    先来看这样一段代码 class Base { public: virtual void print(int a = 1) const { std::cout << "Base & ...

  7. Win10连WiFi显示无internet,安全 却可以正常上网

    1.现象: win10连WiFi显示无internet,安全 可以正常上网 2.原因: Wind10升级系统补丁后,更新了系统检查是否联网的注册表配置,新的域名在国内存在无法连接情况.导致此问题发生 ...

  8. windows中抓包命令,以及保存为多个文件的方法

    本文主要介绍windows中抓包命令,以及保存为多个文件的方法 说一说保存为多个文件存储数据包这个问题的由来,一般如果长时间抓包,有可能需要等上几个小时,因为这个时候抓包的内容都是存放在内存中的,几个 ...

  9. Ubuntu20.04安装 maven并配置阿里源

    Ubuntu20.04安装 maven并配置阿里源 sudo apt update sudo apt install maven #安装maven,默认安装路径为/usr/share/maven 添加 ...

  10. SpringBoot-Thymeleaf模板引擎

    模板引擎,我们其实大家听到很多,其实jsp就是一个模板引擎,还有用的比较多的freemarker,包括SpringBoot给我们推荐的Thymeleaf,模板引擎有非常多,但再多的模板引擎,他们的思想 ...