06.WWDC20-runtime优化
其中主要提到了三点优化
Class数据结构的变化
方法列表的变化
Tagged pointer 格式变化
一、Class数据结构与运行时
1.1 class_ro_t
在二进制文件中,Class的结构是这样的
首先Class对象包含了最常用的信息:指向元类、父类、方法缓存的指针
他还有一个指向更多数据的指针,存储这些额外信息的地方是:class_ro_t
RO
代表 Read only
, 它包含:
类名
方法
协议
实例变量信息等
Swift Class
和OC Class
共享这一数据结构
当类第一次从磁盘加载到内存中时,他的结构是这样的,但是一经使用,他就会发生变化
1.2 Clean memory & Dirty memory
Clean memory
是指加载后不会发生改变的内存
class_ro_t
就是这种
Dirty memory
是指进程运行时会发生改变的内存
Class对象
就是这种,应为它在运行时会发生变化
Dirty memory
要比Clean memory
昂贵的多,只要进程在运行,他就一直存在。Clean memory
可以进行移除,以节省空间;如果需要可以再从磁盘中加载。
之所以将Class的数据分成两部分,是为了尽可能的节省空间
1.3 class_ro_t
运行时需要追踪每个类的更多信息,当一个类首次被使用时,运行时会为他分配额外的存储空间。 class_rw_t
class_rw_t
使用来读写数据的,在这个数据结构中存储了只有在运行时才会生成的信息。
方法、属性、协议也在 class_rw_t
中,是因为运行时可以通过 Category
动态添加。因为 class_ro_t
是只读的所以我们就在 class_rw_t
管理这些。
但是这样会占用很多的内存。那么 如何缩小这部分的体积呢?
二、优化 class_rw_t
虽然每个类有这样的数据结构,但是真正用到这部分读写功能的却只占很少一部分。
只有 Swift
有 Demangled name
,并且只有需要访问 Swift类
的 Objective-c
名称时才会用到它。
我们将 class_rw_t
分为了两部分:
class_rw_t
常用的
class_rw_ext_t
不常用的
这将
class_rw_t
的体积减少了进50%
在有需要的时候将 class_rw_ext_t
插入其中,约 90%
的类用不到这些拓展数据。
三、方法列表
3.1 方法列表元素的结构
selector 方法名
Type Encodings 类型编码
这是一个表示参数类型的字符串。官方文档
指向方法实现的指针
3.2 方法列表元素的大小
在64位系统下,占用24字节。
3.3 寻址
下面的图模拟了内存的分布
方法元素的三个指针指向了App内存空间的相对位置,并在加载时修正为实际的位置。由于在内存中,这三个元素的内存地址是连续的。所以我们可以对寻址进行优化。
3.4 优化:Relative method lists
2020年runtime做了优化,通过32位内存偏移
来进行寻址,简化了寻址操作。
偏移量始终是相同的
无论image从哪里加载到内存中,它们从磁盘加载到内存后无需进行修正
将方法列表存入只读的空间,安全节省内存
使用32位来存储,节省了一半的空间
3.5 Swizzling relative method lists
现在的imp不能引用完整的地址空间,但如果你Swizzle一个方法,他就可以在任何地方实现。
由于方法列表放入了只读的空间,我们并不能直接修改方法列表。这里提供了一个全局表
。这个全局表将方法映射到他们被Swizzle的实现上。
在实际情况下,只会有少量的Swizzle所以这个表不会很大。
四、兼容性
4.1 系统要求
Xcode会对支持更低版本的包按老的方式进行处理。兼容Swift。
4.2 一些SDK
如果我们使用了在其他地方编译好的SDK,但是他又没有兼容 Related method lists
。
在老的OS下运行,如果它通过地址去访问方法,它会将两个段32位数据作为一个64位去进行读取,两个指针粘连在一起,那么就会发生崩溃。
为保证不会出现这样的问题,请使用系统API。
五、Tagged pointer format changes (ARM64)
5.1 Tagged pointer
以前一个对象的指针是这样的结构。我们只用到了中间的部分。
由于低位对齐的原则低三位为0,由于地址空间有限,高位为0。
Tagged pointer
,将第一位设为了1。这是区别它和原始指针的最简单的方式。以 NSNumber 为例:
5.2 Tagged pointer On Intel
最低位1
代表它是
Tagged pointer
接下来3位是:标签位
表示了指针的类型,例如3代表它是
NSNumber
这里有3位,这里最多能有8种类型
剩下的是
Payload
是特定类型可以随意使用的数据对于NSNumber来说,这里就是实际的数字
对于 标签7
有特殊情况
标签7
有特殊情况它表示一个拓展标签
使用接下来的8位来编码类型
这允许多出256个标签类型
但是代价是减少了
Payload
的可用区域这使得我们可以将
Tagged pointer
用于更多类型,只要Payload
能够装入。如:
UIColor
、NSIndexSet
注意
开发者无法添加
Tagged pointer
类型。(OC)Swift中带有关联值的枚举就是一个类似
Tagged pointer
的类。Swift运行时将枚举判别器存储在关联值的
Payload
的备用位中。而且Swift值类型的运用,使得
Tagged pointer
没有那么重要了。If you've ever used an enum with an associated value that's a class, that's like a tagged pointer. The Swift runtime stores the enum discriminator in the spare bits of the associated value payload.
5.3 Tagged pointer On ARM
ARM
下,结构使反过来的。为什么不合Intel上保持一致呢?
优化 objc_msgSend
目的是加快 objc_msgSend
最常见的分支路径。
只用通过最高位是否为1,就可以区分同时检查它是
(nil || 非Tagged pointer)
,节省了条件分支。
优化: iOS 14
在新版系统中,将标签为移到了后3位。
在
ARM64
下,会忽略指针的前8位Top byte ignore
的 ARM特性
注意:以前这样的判断是OK的,但是在iOS14后就不行了。
老老实实使用系统API,它会帮你处理一切。不要为了看起来牛逼而写一些这种危机四伏的代码。
Last updated