Swift4 的弱引用
旧实现
在之前, Swift 的对象有两个引用计数:
- 弱引用计数,
- 强引用计数, 计数为0时对象销毁.
对象销毁, 弱引用的内存并不会自动释放
加载弱引用时, runtime 会对引用对象进行检查, 讲师对象的话会对弱引用技术进行递减操作. 一旦弱引用计数为0时, 对象内存才会被释放. 也就是说, 僵尸对象的所有弱引用被加载访问厚才会被真正清空.
对于拥有很多实例的类, 这会造成内存浪费.
而且并发读取并非线程安全.
OC 对象的内存安排
对象其实是由一组数据组成
- 对象的雷信息 : 内存
- 存储属性(实例变量) : 内存
- 各种引用计数信息 : 外部表 (Swift 旧实现是在内存)
- OC runtime 存储的辅助信息(e.g. 弱引用列表和关联对象) : 外部表
新实现
Side Tables
对于现在内存和空间中权衡的方法, 相较以前内存不足的时候的方法, 现在应该有更好的实现
Swift 在弱引用的新版实现代码中, 引入了 side tables 概念来改进缺陷
Side table: 本质用于保存额外信息的单独内存块, 而且是可选的. i.e. 对于无需保存额外信息的对象来说没有多余的开销
每个对象都有一个指向其对应 side table 的指针,而 side table 也有一个指针指向该对象。另外,side table 可以存储关联的对象数据等其他信息。
为了避免 side table 带来的 8 字节空间开销,Swift 做了一个漂亮的优化。通常内存中的第一个字(Word)是类信息,第二个字则是引用计数。当对象存在 side table 需求时,第二个字将保存指向 side table 的指针。因为引用计数是必要信息,所以此时会将引用计数保存到 side table 中。至于程序运行时到底是哪种情形,则由该块内存中的一个标志位进行区分
通过将弱引用从指向对象本身改为指向 side table ,Swift 得以在保留原有引用计数设计的同时修复了旧设计中的缺陷。
因为 side table 比较小并且弱引用不再指向对象本身,这样之前大型僵尸对象的内存空间将能立即释放从而降低了内存浪费。同时该实现也让线程安全问题变得更易解决:不再需要提前将弱引用置空。因为 side table 比较小,指向它的弱引用可以持续保留,直到这些引用自身被覆写或销毁。
ref: Swift 4 弱引用实现