# 16.iOS应用启动（三）：镜像文件的读取和加载

> 本文基于 `dyld-832.7.3` 与 `objc4-818.2` 源码

## 前言

在上一文[iOS应用启动（二）：环境配置与runtime初始化](https://ryukiedev.gitbook.io/wiki/ios/di-ceng/15.ios-ying-yong-qi-dong-er-objcinit)我们提到了 `_dyld_objc_notify_register`，本文就将深入对镜像文件的读取`map_images`与加载`loadClass`进行解读。

## 一、 \_dyld\_objc\_notify\_register

`_dyld_objc_notify_register(&map_images, load_images, unmap_image);`

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

* 这里有两个重要的函数：
  * map\_images
    * 管理文件中和动态库中的所有符号：`class` `protocol` `selector` `category`
  * load\_images
    * 执行 `+load` 方法
* 进行了镜像文件的读取与加载

***这里有个一个小细节***

我们看到这里调用 `&map_images` 这里用了函数的地址，因为这里非常重要，为了保持整个过程的同步。

## 二、 map\_images

```cpp
/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
```

### 2.1 map\_images\_nolock

```cpp
void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ...

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    ...

    // Call image load funcs after everything is set up.
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}
```

### 2.2 \_read\_images

> 这里的源码非常长，但是`Apple`贴心的加了`Log`我们可以从`Log`找下线索

```cpp
ts.log("IMAGE TIMES: fix up selector references");

ts.log("IMAGE TIMES: discover classes");

ts.log("IMAGE TIMES: remap classes");

ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

ts.log("IMAGE TIMES: discover protocols");

ts.log("IMAGE TIMES: fix up @protocol references");

ts.log("IMAGE TIMES: discover categories");

ts.log("IMAGE TIMES: realize non-lazy classes");

ts.log("IMAGE TIMES: realize future classes");
```

* 整体流程：
  * 条件控制进行第一次加载
  * 修复预编译阶段的 `@selector` 混乱问题
  * 错误混乱的类处理
  * 修复重映射一些没有被镜像文件加再进来的类
  * 修复一些消息
  * 当类中有协议的时候，`readProtocol`
  * 修复没有被加载的协议
  * 处理分类
  * 类的加载相关处理

> ***修复***：主要是指对地址进行修复，将`MachO`中的地址修复为`dyld`处理后的地址

### 2.3 循环调用 readClass 加载类

在这里添加Log可以发现，自定义的类是最后加载的：

```cpp
... // 一系列系统类加载
readClass -- RYModel
readClass -- RYSubModel
```

> 这里如果想过滤系统的可以参考下面的代码：

```cpp
const char *myClassName = "RYModel";
const char *mangledName = cls->nonlazyMangledName();

if (strcmp(mangledName, myClassName) == 0) {
    printf("%s -- %s\n", __func__, myClassName);
}
```

### 2.4 readClass 源码中的坑

下图中的代码，看似进行了Class的ro rw的操作但实际运行断点的时候根本不会执行，千万不要被源码误导了。

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

如图三个断点：

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

跳过了中间的断点

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

### 2.5 寻找类相关的流程

在源码中找到了可能的地方，添加断点：

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

> 实现 `非懒加载类`（实现类`+load`方法或者静态实例）
>
> Realize non-lazy classes (for +load methods and static instances)

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

> Realize newly-resolved future classes

运行后只输出了：

```
Realize non-lazy classes _read_images -- RYModel
```

而在其中也发现来到了另一个重要的流程：`realizeClassWithoutSwift`

> 关于这部分内容之前在研究msgSend的过程中有顺便进行了探索，可以在[类的实现与初始化](https://ryukiedev.gitbook.io/wiki/ios/di-ceng/12.-lei-de-shi-xian-yu-chu-shi-hua)一文中进行深入了解。

### 补充：懒加载类 & 非懒加载类

在`2.5`中提到了 `非懒加载类`，那么什么是 `非懒加载类`，什么是 `懒加载类`？

***懒加载类***

数据加载推迟到第一次接收第一次消息的时候

***非懒加载类***

`map_image` 的时候加载所有类数据。

## 三、 load\_images

* 这里流程就比较简单了
  * 加载分类
  * 查找所有 load 方法
  * 调用 load 方法

```cpp
void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        // 加载分类
        loadAllCategories();
    }

    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    // 查找所有 load 方法
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    /// 调用 load 方法
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
```

## 流程图

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