本文的主要目的是探索 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. js中针对dom的crud

    1.怎样添加.移除.移动.复制.创建和查找节点? 1)创建新节点 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 cr ...

  2. javascript 标签切换

    * index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=&q ...

  3. VMware安装最新版CentOS7图文教程

    https://blog.csdn.net/reticent_man/article/details/80732395 https://blog.csdn.net/q2158798/article/d ...

  4. jquery 设置django全局token

    通过JQUEYR中的ajaxSetup,来设置django中的token,即不需要再每次都去引用: 第一步: 先django中的html中设置 {%  csrf_token %} 第二步: 新一个js ...

  5. setTimeout 与setInterval的区别

    setTimeout(code,millisec) 方法用于在指定的毫秒数后调用函数或计算表达式 setInterval(code,millisec) 方法可按照指定的周期(以毫秒计)来调用函数或计算 ...

  6. AVS 端能力之音频播放模块

    功能简介 音频播放 音频流播放 URL文件播放 播放控制 播放 暂停 继续 停止 其它功能(AVS服务器端实现) 支持播放列表 支持上一首下一首切换 支持电台 事件指令集 AudioPlayer 端能 ...

  7. Fiddler抓包(以谷歌浏览器、安卓手机为例)

    fiddler抓包流程与whistle相同,所以本章内容会相对简洁.如果需要详细说明,可参考whistle抓包. 这里以谷歌浏览器.安卓手机为例. 1.fiddler安装 下载安装包,默认安装. 2. ...

  8. vue-cli3 取消eslint 校验代码

    项目生成后会有个.eslintrc.js文件 module.exports = { root: true, env: { node: true }, 'extends': [ 'plugin:vue/ ...

  9. Jmeter压测学习6---登录参数CSV

    前言 我们在压测登录接口的时候,如果只用一个账号去设置并发压测,这样的结果很显然是不合理的,一个用户并发无法模拟真实的情况.如果要压测登录接口,肯定得准备几百,甚至上千的账号去登录,测试的结果才具有可 ...

  10. Xcode相关

    Xcode相关的路径 Provisioning Profiles存放路径:~/Library/MobileDevice/Provisioning Profiles 所有模拟器(包括历史模拟器):~/L ...