官网链接WWDC20-Advancements in the Objective-C runtimearrow-up-right
其中主要提到了三点优化
一、Class数据结构与运行时
在二进制文件中,Class的结构是这样的
首先Class对象包含了最常用的信息:指向元类、父类、方法缓存的指针
他还有一个指向更多数据的指针,存储这些额外信息的地方是:class_ro_t
RO 代表 Read only, 它包含:
Swift Class 和 OC Class 共享这一数据结构
当类第一次从磁盘加载到内存中时,他的结构是这样的,但是一经使用,他就会发生变化
1.2 Clean memory & Dirty memory
Dirty memory
Class对象 就是这种,应为它在运行时会发生变化
Dirty memory 要比 Clean memory 昂贵的多,只要进程在运行,他就一直存在。Clean memory 可以进行移除,以节省空间;如果需要可以再从磁盘中加载。
之所以将Class的数据分成两部分,是为了尽可能的节省空间
运行时需要追踪每个类的更多信息,当一个类首次被使用时,运行时会为他分配额外的存储空间。 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 的体积减少了进 50%
在有需要的时候将 class_rw_ext_t 插入其中,约 90% 的类用不到这些拓展数据。
Type Encodings 类型编码
这是一个表示参数类型的字符串。官方文档arrow-up-right
在64位系统下,占用24字节。
下面的图模拟了内存的分布
方法元素的三个指针指向了App内存空间的相对位置,并在加载时修正为实际的位置。由于在内存中,这三个元素的内存地址是连续的。所以我们可以对寻址进行优化。
3.4 优化:Relative method lists
2020年runtime做了优化,通过32位内存偏移来进行寻址,简化了寻址操作。
偏移量始终是相同的
无论image从哪里加载到内存中,它们从磁盘加载到内存后无需进行修正
3.5 Swizzling relative method lists
现在的imp不能引用完整的地址空间,但如果你Swizzle一个方法,他就可以在任何地方实现。
由于方法列表放入了只读的空间,我们并不能直接修改方法列表。这里提供了一个全局表。这个全局表将方法映射到他们被Swizzle的实现上。
在实际情况下,只会有少量的Swizzle所以这个表不会很大。
Xcode会对支持更低版本的包按老的方式进行处理。兼容Swift。
如果我们使用了在其他地方编译好的SDK,但是他又没有兼容 Related method lists。
在老的OS下运行,如果它通过地址去访问方法,它会将两个段32位数据作为一个64位去进行读取,两个指针粘连在一起,那么就会发生崩溃。
为保证不会出现这样的问题,请使用系统API。
5.1 Tagged pointer
以前一个对象的指针是这样的结构。我们只用到了中间的部分。
由于低位对齐的原则低三位为0,由于地址空间有限,高位为0。
Tagged pointer,将第一位设为了1。这是区别它和原始指针的最简单的方式。以 NSNumber 为例:
5.2 Tagged pointer On Intel
接下来3位是:标签位
表示了指针的类型,例如3代表它是 NSNumber
剩下的是 Payload 是特定类型可以随意使用的数据
这使得我们可以将 Tagged pointer 用于更多类型,只要 Payload 能够装入。
开发者无法添加 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),节省了条件分支。
在新版系统中,将标签为移到了后3位。
注意:以前这样的判断是OK的,但是在iOS14后就不行了。
老老实实使用系统API,它会帮你处理一切。不要为了看起来牛逼而写一些这种危机四伏的代码。