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

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

## 前言

在上一文[iOS应用启动（二）：环境配置与runtime初始化](/wiki/ios/di-ceng/15.ios-ying-yong-qi-dong-er-objcinit.md)我们提到了 `_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](/files/-MfBKqeCTMgyvTy5eo1o)

* 这里有两个重要的函数：
  * 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的操作但实际运行断点的时候根本不会执行，千万不要被源码误导了。

![坑](/files/-Meeo1Yo4BNwdlJcd6OY)

如图三个断点：

![坑](/files/-Meeo1YpPg2nZk_cnOXd)

跳过了中间的断点

![坑](/files/-Meeo1Yq6e9-skPLNwQK)

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

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

![](/files/-MfBKqeF7PhXUWOecrpD)

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

![](/files/-MfBKqeGO2XgRa3hbGPm)

> Realize newly-resolved future classes

运行后只输出了：

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

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

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

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

在`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();
}
```

## 流程图

![](/files/-MfBKqeJC00cGhNHuQ0K)


---

# 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/16.ios-ying-yong-qi-dong-san-jing-xiang-wen-jian-de-du-qu-he-jia-zai.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.
