# 17.iOS应用启动（四）：分类的加载

## 前言

我们在[12.类的实现与初始化](https://ryukiedev.gitbook.io/wiki/ios/di-ceng/12.-lei-de-shi-xian-yu-chu-shi-hua)中实现类 `realizeClass` 的流程最后一步发现是处理分类 `methodizeClass`。本文就将以此为入口，深入探索 ***什么是分类***。

## 一、 methodizeClass

处理`方法列表`、`协议列表`、`属性列表`，添加`分类`。

> 重要部分见注释：

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

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

### 1.1 attachToClass 将分类添加到类

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

> 继续调用 `attachCategories`，通过初步阅读源码，发现此处为核心步骤，下面进行具体分析

## 二、 attachCategories

> 这里对源码中两处重要的注释做翻译：

* 添加分类的方法列表、属性、协议到类
* 最早加载的分类在最顶部
* 只有很少的类在运行时会有超过`64`个分类
* 这里使用一个小容量的`栈`
* 分类必须按照合适的顺序添加，这里会从前向后读取，从后向前创建本地缓存，并调用 `attachLists`。所以最后的顺序是对的。
*

```cpp
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
。。。
    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    constexpr uint32_t ATTACH_BUFSIZ = 64;
    method_list_t   *mlists[ATTACH_BUFSIZ];
    property_list_t *proplists[ATTACH_BUFSIZ];
    protocol_list_t *protolists[ATTACH_BUFSIZ];

    uint32_t mcount = 0;
    uint32_t propcount = 0;
    uint32_t protocount = 0;
    bool fromBundle = NO;
    bool isMeta = (flags & ATTACH_METACLASS);
    // 创建 rwe
    auto rwe = cls->data()->extAllocIfNeeded();

    // 循环添加方法、属性、协议
    for (uint32_t i = 0; i < cats_count; i++) {
        auto& entry = cats_list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            if (mcount == ATTACH_BUFSIZ) {
                prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                rwe->methods.attachLists(mlists, mcount);
                mcount = 0;
            }
            mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist =
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            if (propcount == ATTACH_BUFSIZ) {
                rwe->properties.attachLists(proplists, propcount);
                propcount = 0;
            }
            proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
        if (protolist) {
            if (protocount == ATTACH_BUFSIZ) {
                rwe->protocols.attachLists(protolists, protocount);
                protocount = 0;
            }
            protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
        }
    }

    if (mcount > 0) {
        prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                           NO, fromBundle, __func__);
        rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
        if (flags & ATTACH_EXISTING) {
            flushCaches(cls, __func__, [](Class c){
                // constant caches have been dealt with in prepareMethodLists
                // if the class still is constant here, it's fine to keep
                return !c->cache.isConstantOptimizedCache();
            });
        }
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
```

### 2.1 流程

* extAllocIfNeeded 创建 rwe
* 循环添加方法、属性、协议

## 三、 分类加载的时机

我们直到类添加分类的核心逻辑是 `attachCategories`。我们全局搜索一下 `attachCategories`。

***attachToClass***

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

***load\_categories\_nolock***

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

有上面两处进行了调用。

### 3.1 测试代码准备

***自定义类***

```
@interface RYCat : NSObject

@property (nonatomic, copy) NSString *nick;
@property (nonatomic, assign) NSInteger age;

- (void)yeah;
- (void)yeah2;
- (void)yeah3;

@end
```

***分类***

```
@interface RYCat (CateA)

- (void)funcA;
- (void)funcA2;
- (void)funcA3;

@end
```

***main函数***

```
RYCat *cat = [RYCat alloc];
[cat funcA];
```

### 3.1 添加断点及日志

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

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

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

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

### 3.2 场景一： 分类有Load✅ 主类有Load✅ （非懒加载）

Log如下：

```cpp
_read_images -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
load_categories_nolock -- RYCat -- CateA
attachCategories -- RYCat -- CateA
attachCategories -- RYCat -- CateA
+[RYCat load]
+[RYCat(CateA) load]
-[RYCat(CateA) funcA]
Program ended with exit code: 0
```

***流程：***

* \_read\_images
  * realizeClassWithoutSwift
    * attachToClass
      * load\_categories\_nolock
        * attachCategories

### 3.3 场景二： 分类有Load✅ 主类无Load❌ （非懒加载）

Log如下：

```cpp
_read_images -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
+[RYCat(CateA) load]
-[RYCat(CateA) funcA]
Program ended with exit code: 0
```

***流程：***

* \_read\_images
  * realizeClassWithoutSwift
    * attachToClass

> 注意到：***并没有调用 `attachCategories`***。

#### 非懒加载

![非懒加载](https://4193904735-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MI8JgbGh3U6X_oedqkm%2Fsync%2F3b07f549b84f1946faa96f7f3fccf76b9871b44b.png?generation=1627801460082214\&alt=media)

虽然主类未实现+load方法，但是分类实现了，那么主类也是非懒加载的。

### 3.4 场景三： 分类无Load❌ 主类有Load✅ （非懒加载）

Log如下：

```cpp
_read_images -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
+[RYCat load]
-[RYCat(CateA) funcA]
Program ended with exit code: 0
```

***流程：***

* \_read\_images
  * realizeClassWithoutSwift
    * attachToClass

> 注意到：和 ***分类有Load✅ 主类无Load❌*** 一样，***没有调用 `attachCategories`***。

### 3.5 场景四： 分类无Load❌ 主类无Load错❌ （懒加载）

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

Log如下：

```
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
realizeClassWithoutSwift -- RYCat
attachToClass -- RYCat
Program ended with exit code: 0
```

> 注意到：和 ***3.3和3.4*** 一样，***没有调用 `attachCategories`***。

### 思考： 为什么没有调用 `attachCategories`

`编译器`优化了，直接把分类的数据合并到了主类中。

## 四、 多分类 `+load` 执行顺序

***如果有多个分类，执行的顺序是什么样的呢？***

如图创建两个分类，分别实现 `+load` 方法。思考一下执行的顺序是什么样的呢？为什么呢？

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

### 4.1 顺序

按顺序输出如下：

* `+[RYCat load]`
* `+[RYCat(CateA) load]`
* `+[RYCat(CateB) load]`

我们先看下编译顺序：

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

调整一下编译顺序看有没有影响：

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

调整编译顺序后输出如下：

* `+[RYCat load]`
* `+[RYCat(CateB) load]`
* `+[RYCat(CateA) load]`

### 4.2 结论

多分类的情况下，`+load` 的执行顺序是和编译的顺序有关的。

## 五、 多分类同名方法执行顺序

如果多个分类由同名的方法，又会是怎样的顺序呢？

***在两个分类中声明实现同名的\*\*\*\* \*\*\*\*`funcA`***

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

> 在 [多分类 `+load` 执行顺序](#四-多分类-load-执行顺序) 中我们知道，编译的顺序影响了各个分类的表现，这里我们也进行一下区分

### 5.1 CateA 比 CateB 先编译

* +\[RYCat load]
* +\[RYCat(CateA) load]
* +\[RYCat(CateB) load]
* -\[RYCat(CateB) funcA]

### 5.2 CateB 比 CateA 先编译

* +\[RYCat load]
* +\[RYCat(CateA) load]
* +\[RYCat(CateB) load]
* -\[RYCat(CateA) funcA]

### 5.3 在主类中也声明并实现 funcA （CateB 比 CateA 先编译）

* +\[RYCat load]
* +\[RYCat(CateA) load]
* +\[RYCat(CateB) load]
* -\[RYCat(CateA) funcA]

### 5.4 总结

可见：多个分类拥有`同名方法`时，`先编译的分类的方法`会被`后编译的分类`覆盖掉。如果主类中也有同名方法也会被覆盖掉。

## 思考

***对启动速度的影响***

在 ***3.2*** 主类分类都有实现 `Load方法` 的时候可见整个流程流程会复杂一下，所以减少这种场景的出现，是有助于提升应用启动速度。
