# 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)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ryukiedev.gitbook.io/wiki/ios/di-ceng/06.wwdc20runtime-you-hua.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
