# 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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2ef2667edccf6dbd75ecb47b651b253d499cb3ae.png?generation=1624708580939010\&alt=media)

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

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

![class\_ro\_t](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe118d52e1ca63a3a17b8f3731ed63a200d227845.png?generation=1624708581992477\&alt=media)

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

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

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

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

### 1.2 Clean memory & Dirty memory

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

![CD](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fd685c1713ee157dd0adc5508d92258d280867bdb.png?generation=1624708561092794\&alt=media)

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

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

### 1.3 class\_ro\_t

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F8a1f07ce9e8b8f6d0e0d7813547d8bc8265a8933.png?generation=1624708575994238\&alt=media)

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fa8b497bc891370628d43cab18151c873c40b9266.png?generation=1624708562078603\&alt=media)

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fdb376f432f1d6c9b3165b79129c11e617a3a0856.png?generation=1624708571018375\&alt=media)

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

## 二、优化 class\_rw\_t

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F0f473ea0759a1158bc7e9d456bc78b8dbecf9de5.png?generation=1624708574097943\&alt=media)

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fbaf5aecd21ce476fedf120ed16b378536bd14a0f.png?generation=1624708571990249\&alt=media)

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F2ae49f6eeb59eeb7b9e71bbf0ff086711e574b05.png?generation=1624708582641436\&alt=media)

## 三、方法列表

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F7ca76fb25a597545183f6db7542adf8cc6dab8b2.png?generation=1624708575204839\&alt=media)

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

#### selector 方法名

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fade0bcd2842b7f5fab98cd7954944f0474051c00.png?generation=1624708562935536\&alt=media)

#### Type Encodings 类型编码

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F07070a8d217d9d8d89bbd6bb81541b9406558e74.png?generation=1624708564443275\&alt=media)

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe306c066bc04e57e4d32bdd8f99756c802edf51f.png?generation=1624708576992582\&alt=media)

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe670821fab5d4f64e9fe86b9bd714abae5093f0f.png?generation=1624708565983049\&alt=media)

### 3.3 寻址

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F3793845cea97f115ece0d27be7b3d25c9dda6169.png?generation=1624708573138561\&alt=media)

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F431d770c2b9a4547d159d83af6ccd5d714b52392.png?generation=1624708578239687\&alt=media)

### 3.4 优化：Relative method lists

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fe7eac3f37b457cf6f772d2c338a3fb6fe1c53ca1.png?generation=1624708582542331\&alt=media)

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

### 3.5 Swizzling relative method lists

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Ff282428f924fdacee0d79b226b5e51eeba98104a.png?generation=1624708559981615\&alt=media)

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F12d494804d4fa08af64689a803f77d6e8349018a.png?generation=1624708567174845\&alt=media)

在实际情况下，只会有少量的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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F10687be3acf8a906b62e24ee84f696fb101784eb.png?generation=1624708556014941\&alt=media)

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F7170f6bfdbf7884dd73afc8b0ed3b8d6fda72751.png?generation=1624708559228325\&alt=media)

## 五、Tagged pointer format changes (ARM64)

### 5.1 Tagged pointer

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F5b8bfcea5a1e07aaa8ee3054bec9a90563ef615f.png?generation=1624708558283847\&alt=media)

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

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F9d9af7c30eddd88de27a0698d73ffdf0953f66cb.png?generation=1624708555030647\&alt=media)

### 5.2 Tagged pointer On Intel

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F8434a05d41beecc6db9bcddfbad3a2b5158ccd30.png?generation=1624708558065418\&alt=media)

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F913584358dbe27a3113f7d17c674a6c25164d363.png?generation=1624708582180823\&alt=media)

* 它表示一个拓展标签
* 使用接下来的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](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fb5f8ad51702c4285a68e3d0b1e32812eebf5c794.png?generation=1624708569937775\&alt=media)

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

#### 优化 objc\_msgSend

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fc5a0f1f5e1ceb3c4a6ab049685b84178e1c6bf07.png?generation=1624708568035867\&alt=media)

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

#### 优化： iOS 14

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Faed91e31e0608a43927836900129a4f663c84d8c.png?generation=1624708579912245\&alt=media)

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F1c4ac25950309223620095fce28a2ac3cc97fd3c.png?generation=1624708564984749\&alt=media)

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

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2Fa0feb47775a5a2be445dc4f4cffc926c6e4ed6a2.png?generation=1624708568909317\&alt=media)

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

![RW](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F57031de0f4ea349877d483f1e2bbb1d3d9ae1efd.png?generation=1624708578975326\&alt=media)
