WWDC-关于 runtime 的优化
前言
此次优化是 WWDC-2020 提出的,下面可自行观看视频:
用作者话来说,开发者无需更改任何代码,也不使用新的API,应用程序也会变得更快。因为
Apple
在内部运行时做了底层数据结构的优化。
数据结构的变化(Class Data Structures Changes)
在磁盘上,在app的二进制文件中,类的结构如下:
对于类的对象本身,它包含最常访问的信息:
- 指向元类、超类和方法缓存的指针。
- 指向存储附加信息的更多数据的指针,称为
class_ro_t
。其中Ro
代表只读。
class_ro_t
包括类的名称以及方法、协议和实例变量的信息。并且 Swift
和 Objective-C
共享这一基础结构。
当类第一次从磁盘加载到内存中时,它们一开始是固定的,但是一旦被使用,它们就会改变。为了理解接下来的类的变化,首先了解一下
Clean Memory
和Dirty Memory
。
Clean Memory 和 Dirty Memory
-
Clean Memory
是指加载后不会发生更改的内存。class_ro_t
就是属于Clean Memory
,因为它是只读的。 -
Dirty Memory
是指在进程运行时发生更改的内存。Class
一旦被使用就会变成Dirty Memory
,因为运行时会将新数据写入类中。
特点:
Dirty Memory
只要进程在运行,它就一直存在。而且iOS
不比macOS
可以选择交换Dirty Memory
,iOS
不使用swap
,所以Dirty Memory
在iOS
中代价很大。
Clean Memory
可以进行移除,从而节省更多的内存空间。如果你再次需要它,系统可以从磁盘中重新加载
。
这也是 Class
数据被分成两部分的原因,可以保持干净的数据越多越好,分离出不改变的数据,存储为 Clean Memory
。
class_rw_t
虽然这些数据足以使用,但运行时需要跟踪有关每个类的更多信息,因此当类第一次被使用时,运行时还会为它分配额外的存储空间。
这个运行时分配的存储容量是 class_rw_t
,用于读/写数据。在 class_rw_t
中,存储了只有在运行时才会生成的新信息。
First Subclass
和Next Sibling Class
:由于class_rw_t
中的First Subclass
和Next Sibling Class
的特性,所有类都会链接成一个树状结构
。它们允许运行时遍历当前使用的所有类
Methods
、Properties
、Protocols
:在运行时进行添加的,当Category
被加载时,它可以向类中添加新的方法,也可以通过Runtime API
动态添加。
Demangled Name
:只有Swift
才会使用的字段,甚至Swift
类都不需要它。除非开发者想要访问Swfit
的OC
名称,利用率比较低。
class_rw_t 拆分
因为 class_rw_t
里面有太多的东西,会占用 很多
的内存。可以将 不常用
的部分拆分,分配到另一个扩展记录以供类使用。
在实际设备上检查使用情况时,发现只有大约
10%
的类真正改变了Methods
。所以,Methods
、Properties
、Protocols
可以被拆分。
Demangled Name
:只有Swift
才会使用,也可以被拆分。
因此拆分效果如图:
class_rw_t 与 class_ro_t 的区别
当类使用了
category
的时候,那么类就有了class_rw_t
的结构,如果未使用category
,那么类就是一个单纯的class_ro_t
的结构 (Clean Memory
)反之,在类的内存结构中如果有
class_rw_t
的结构,那么必然会有category
案例
实际验证
Xcode
和Safari
的class_rw_t
占用的内存
$ heap Xcode | egrep 'class_rw|COUNT'
占用内存如下:
Xcode:
class_rw_t
占用字节:2877568
class_rw_ext_t
占用字节:203664,占比 7%Safari:
class_rw_t
占用字节:186496
class_rw_ext_t
占用字节:27312,占比 15%
总结:class_rw_ext_t
的拆分,可以节省内存开销。