> For the complete documentation index, see [llms.txt](https://ryukiedev.gitbook.io/wiki/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ryukiedev.gitbook.io/wiki/ios/di-ceng/06.wwdc20runtime-you-hua.md).

# 06.WWDC20-runtime优化

> 官网链接[WWDC20-Advancements in the Objective-C runtime](https://developer.apple.com/videos/play/wwdc2020/10163/)

其中主要提到了三点优化

* Class数据结构的变化
* 方法列表的变化
* Tagged pointer 格式变化

## 一、Class数据结构与运行时

### 1.1 class\_ro\_t

在二进制文件中，Class的结构是这样的

![Class](/files/-Md7AGIPyyaxW7aKJdLD)

首先Class对象包含了最常用的信息：指向元类、父类、方法缓存的指针

他还有一个指向更多数据的指针，存储这些额外信息的地方是：`class_ro_t`

![class\_ro\_t](/files/-Md7AGIQH91PnYu9OggT)

`RO` 代表 `Read only`， 它包含：

* 类名
* 方法
* 协议
* 实例变量信息等

> `Swift Class` 和 `OC Class` 共享这一数据结构

***当类第一次从磁盘加载到内存中时，他的结构是这样的,但是一经使用，他就会发生变化***

### 1.2 Clean memory & Dirty memory

* Clean memory
  * 是指加载后不会发生改变的内存
  * `class_ro_t` 就是这种
* Dirty memory
  * 是指进程运行时会发生改变的内存
  * `Class对象` 就是这种，应为它在运行时会发生变化

![CD](/files/-Md7AGIRA2kirU8OP5Cg)

> `Dirty memory` 要比 `Clean memory` 昂贵的多，只要进程在运行，他就一直存在。`Clean memory` 可以进行移除，以节省空间；如果需要可以再从磁盘中加载。

> 之所以将Class的数据分成两部分，是为了尽可能的节省空间

### 1.3 class\_ro\_t

运行时需要追踪每个类的更多信息，当一个类首次被使用时，运行时会为他分配额外的存储空间。 `class_rw_t`

![RW](/files/-Md7AGIS7qXbTfm4Yp-c)

`class_rw_t` 使用来读写数据的，在这个数据结构中存储了只有在运行时才会生成的信息。

![RW](/files/-Md7AGIU1WczvO4K3R_W)

方法、属性、协议也在 `class_rw_t` 中，是因为运行时可以通过 `Category` 动态添加。因为 `class_ro_t` 是只读的所以我们就在 `class_rw_t` 管理这些。

![RW](/files/-Md7AGIV30UQiuGwr7nf)

但是这样会占用很多的内存。那么 `如何缩小这部分的体积呢？`

## 二、优化 class\_rw\_t

虽然每个类有这样的数据结构，但是真正用到这部分读写功能的却只占很少一部分。

只有 `Swift` 有 `Demangled name`，并且只有需要访问 `Swift类` 的 `Objective-c` 名称时才会用到它。

![RW](/files/-Md7AGIWAts_JZqmNsPe)

我们将 `class_rw_t` 分为了两部分：

* class\_rw\_t
  * 常用的
* class\_rw\_ext\_t
  * 不常用的

![RW](/files/-Md7AGIXspoo4VGH8a4E)

> 这将 `class_rw_t` 的体积减少了进 `50%`

在有需要的时候将 `class_rw_ext_t` 插入其中，约 `90%` 的类用不到这些拓展数据。

![RW](/files/-Md7AGIYDp5VgTlKLq-W)

## 三、方法列表

![RW](/files/-Md7AGIZhxHiPgLJuBxe)

### 3.1 方法列表元素的结构

#### selector 方法名

![RW](/files/-Md7AGI_Xo9A-pEIdoYF)

#### Type Encodings 类型编码

![RW](/files/-Md7AGIa-FBnPIDbOMyF)

这是一个表示参数类型的字符串。[官方文档](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html)

#### 指向方法实现的指针

![RW](/files/-Md7AGIbHyxStZIwUUDr)

### 3.2 方法列表元素的大小

在64位系统下，占用24字节。

![RW](/files/-Md7AGIcubOUvGd6B-uU)

### 3.3 寻址

下面的图模拟了内存的分布

![RW](/files/-Md7AGIdF01aatsL5EVN)

方法元素的三个指针指向了App内存空间的相对位置，并在加载时修正为实际的位置。由于在内存中，这三个元素的内存地址是连续的。所以我们可以对寻址进行优化。

![RW](/files/-Md7AGIeFm2Tuizcx7qb)

### 3.4 优化：Relative method lists

2020年runtime做了优化，通过`32位内存偏移`来进行寻址，简化了寻址操作。

![RW](/files/-Md7AGIfhHVLEg68qQ25)

* 偏移量始终是相同的
  * 无论image从哪里加载到内存中，它们从磁盘加载到内存后无需进行修正
* 将方法列表存入只读的空间，安全节省内存
* 使用32位来存储，节省了一半的空间

### 3.5 Swizzling relative method lists

![RW](/files/-Md7AGIgSWBhH2V0G_5S)

现在的imp不能引用完整的地址空间，但如果你Swizzle一个方法，他就可以在任何地方实现。

由于方法列表放入了只读的空间，我们并不能直接修改方法列表。这里提供了一个`全局表`。这个全局表将方法映射到他们被Swizzle的实现上。

![RW](/files/-Md7AGIhX7Jo_sVxRb0a)

在实际情况下，只会有少量的Swizzle所以这个表不会很大。

## 四、兼容性

### 4.1 系统要求

|      支持版本     |
| :-----------: |
| macOS Big Sur |
|     iOS 14    |
|    tvOS 14    |
|   watchOS 7   |

> Xcode会对支持更低版本的包按老的方式进行处理。兼容Swift。

### 4.2 一些SDK

如果我们使用了在其他地方编译好的SDK，但是他又没有兼容 `Related method lists`。

在老的OS下运行，如果它通过地址去访问方法，它会将两个段32位数据作为一个64位去进行读取，两个指针粘连在一起，那么就会发生崩溃。

![RW](/files/-Md7AGIi1Q8tG-0XUDDD)

为保证不会出现这样的问题，请使用系统API。

![RW](/files/-Md7AGIjkGlKuXLXW-EX)

## 五、Tagged pointer format changes (ARM64)

### 5.1 Tagged pointer

![RW](/files/-Md7AGIkHHs1S3dxQ4oS)

以前一个对象的指针是这样的结构。我们只用到了中间的部分。

由于低位对齐的原则低三位为0，由于地址空间有限，高位为0。

`Tagged pointer`，将第一位设为了1。这是区别它和原始指针的最简单的方式。以 NSNumber 为例：

![RW](/files/-Md7AGIlgEdN3-tEpU5l)

### 5.2 Tagged pointer On Intel

![RW](/files/-Md7AGImx8cxO0a7oQtG)

* 最低位1
  * 代表它是 `Tagged pointer`
* 接下来3位是：标签位
  * 表示了指针的类型，例如3代表它是 `NSNumber`
  * 这里有3位，这里最多能有8种类型
* 剩下的是 `Payload` 是特定类型可以随意使用的数据
  * 对于NSNumber来说，这里就是实际的数字

#### 对于 `标签7` 有特殊情况

![RW](/files/-Md7AGInFek2Ct4ZZSlc)

* 它表示一个拓展标签
* 使用接下来的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

![RW](/files/-Md7AGIsbESxd6_HTd1q)

`ARM` 下，结构使反过来的。为什么不合Intel上保持一致呢？

#### 优化 objc\_msgSend

目的是加快 `objc_msgSend` 最常见的分支路径。

![RW](/files/-Md7AGItvMrgsFP6idlu)

> 只用通过最高位是否为1，就可以区分同时检查它是 `（nil || 非Tagged pointer）`，节省了条件分支。

#### 优化： iOS 14

![RW](/files/-Md7AGIuIV62ShzLcAZF)

在新版系统中，将标签为移到了后3位。

![RW](/files/-Md7AGIwRmbi7ZoSE6eW)

* 在`ARM64`下，会忽略指针的前8位
  * `Top byte ignore` 的 ARM特性

> 注意：以前这样的判断是OK的，但是在iOS14后就不行了。

![RW](/files/-Md7AGIxBI5NLrA_WYP7)

> 老老实实使用系统API，它会帮你处理一切。不要为了看起来牛逼而写一些这种危机四伏的代码。

![RW](/files/-Md7AGIyHRHj2mho2OKI)
