在 WWDC2020 中 Objective-C 运行时的改进这个视频提到关于类的的数据结构的一些变化,本文是对这个视频提到的部分变化进行翻译。
如果想了解更详细的内容,请移步去官网看这个视频:
developer.apple.com/wwdc20/1016…。
视频中介绍了三个变化:
- 首先是数据结构的变化, Objective-C 运行时会使用它们来追踪类。
- 其次是 Objective-C 方法列表的变化。
- 最后是 tagged pointer 格式的变化。
一、数据结构的变化
类对象本身包含了最常被访问的信息:指向元类、超类和方法缓存的指针,它还有一个指向更多数据的指针,存储额外信息的地方叫做 class_ro_t。
“ro”代表只读,它包括像类名词,方法,协议,和实例变量的信息。Swift 类和 Objective-C 类共享这一数据结构,所以每个 Swift 类也有这些数据结构。
当类第一次从磁盘中加载到内存中时,它们一开始也是这样的,但一经使用,它们就会发生变化。
了解这些变化之前,先了解一下 clean memory 和 dirty memory 的区别。
* clean memory:指加载后不会发生更改的内存。class_ro_t 就属于 clean memory,因为它是只读的。
* dirty memory:指在进程运行时会发生更改的内存。类结构一经使用就会变成 dirty memory,因为运行时会向它写入新的数据。例如,创建一个新的方法缓存并从类中指向它。
dirty memory 比 clean memory 要昂贵得多,只要进程在运行,它就必须一直存在 。另一方面 clean memory 可以进行移除,从而节省更多的内存空间,当需要使用 clean memory 的时候系统可以从磁盘中重新加载。
macOS 可以选择唤出 dirty memory,但因为 iOS 不使用 swap,所以 dirty memory 在iOS中的代价很大。
虽然这些数据足以让我们开始,但运行时需要追踪每个类的更多信息,所以当一个类首次被使用,运行时会为它分配额外的存储容量。
这个运行时分配的存储容量是 class_rw_t 用于读取-编写数据,在这个数据结构中,我们存储了只有在运行时才会生成的新信息,First Subclass,Next Sibling Class。
例如,所有的类都会链接成一个树状结构,这是通过使用 First Subclass,Next Sibling Class 指针实现的,这允许运行时遍历当前使用的所有类,这对于使方法缓存无效非常有用。
但为什么方法和属性也在只读数据中时,这里还要有方法和属性呢?
因为它们可以在运行时进行更改,当 category 被加载时,它可以向类中添加新的方法,而且程序员可以使用运行时 API 动态的添加它们,而 class_ro_t 是只读的,所以我们需要在 class_rw_t 中追踪这些东西。
只有 Swift 类会使用 demangled name 字段,并且 Swift 类并不需要这一字段,除非有东西访问它们的 Objective-C 名称时才需要。
所以我们可以拆掉那些平时不用的部分-class_rw_ext_t,这将 class_rw_t 的大小减少了一半。
对于那些确实需要额外信息的类,我们可以分配这些扩展记录中的一个,并把它滑到类中供其使用。
二、Objective-C方法列表的变化
每一个类都附带一个方法列表,当你在类上编写新方法时,它就会被添加到列表中。运行时使用这些列表来解析消息发送。
每个方法都包含三个信息。
- 首先是方法的名称,或者说选择器,选择器时字符串,但它们具有唯一性,所以它们可以使用指针相等来进行比较。
- 接下来是方法的类型编码 这是一个表示参数和返回类型的字符串 它不是用来发送消息的 但它是运行时 introspection 和消息 forwarding 所必需的东西。
- 最后,还有一个指向方法实现的指针,方法的实际代码,当你编写一个方法时,它会编译成一个 c 函数,其中包含你的实施,然后方法列表中的 entry 会指向该函数。
三、tagged pointer
什么是 tagged pointer 呢?0x00000001003041e0 的二进制表示如图所示:
我们把它分解成二进制表示法 我们有 64 位 然而 我们并没有真正地使用到所有这些位。
- 我们只在一个真正的对象指针中 使用了中间的这些位。
- 由于对齐要求的存在 低位始终为 0 对象必须总是位于 指针大小倍数的一个地址中。
- 由于地址空间有限 所以高位始终为 0 我们实际上不会用到 2^64。
0000 0000 0000 0000 0000 0000 0000 0001 0000 0000 0011 0000 0100 0001 0001(0000 变成 0001)
(滑动显示更多)
- 这些高位和低位总是 0 所以 让我们从这些始终为 0 的位中 选择一个位并把它设置为 1,这可以让我们立即知道 这不是一个真正的对象指针 然后我们可以给其他所有位 赋予一些其他的意义 我们称这种指针为 tagged pointer。
例如 我们可以在其他位中塞入一个数值 只要我们想教 NSNumber 如何读取这些位 并让运行时适当地处理 tagged pointer 系统的其他部分就可以 把这些东西当做对象指针来处理 并且永远不会知道其中的区别。
这样可以节省我们为每一种类似情况,分配一个小数字对象的代价,这是一个重大的改进。
来源:稀土掘金
作者:Coder_张三
链接:https://juejin.cn/post/7040747023481438222