# 15.iOS应用启动（二）：环境配置与runtime初始化

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

## 前言

结合上一文[iOS应用启动（一）：\_dyld\_start](https://ryukiedev.gitbook.io/wiki/ios/di-ceng/14.ios-ying-yong-qi-dong-yi-dyldstart)及符号断点我们找到了 `_objc_init` 的调用栈

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

* dyld\`ImageLoaderMachO::doModInitFunctions
* libSystem.B.dylib\`libSystem\_initializer
* libdispatch.dylib\`libdispatch\_init
* libdispatch.dylib\`\_os\_object\_init
* libobjc.A.dylib\`\_objc\_init

## 一、 \_objc\_init 做了什么

* 引导初始化，使用 dyld 注册 images。
* 在库初始化时间之前由 libSystem 调用

```cpp
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
```

### 1.1 源码分析

```cpp
void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?
    /// 初始化环境变量
    environ_init();

    /// 关于线程`key`的绑定：比如线程数据的析构函数
    tls_init();

    /// 调用全局静态C++构造函数
    static_init();

    /// runtime 运行时环境初始化
    runtime_init();

    /// 初始化 `libobjc` 的异常处理系统
    exception_init();

#if __OBJC2__
    /// 缓存条件初始化
    cache_t::init();
#endif
    /// 启动回调机制
    _imp_implementationWithBlock_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);

#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}
```

下面我们将对一些重要步骤进行说明：

* environ\_init
  * 初始化环境变量
* tls\_init
  * 关于线程`key`的绑定：比如线程数据的析构函数
* static\_init
  * 调用全局静态C++构造函数
* runtime\_init
  * runtime 运行时环境初始化
* exception\_init
  * 初始化 `libobjc` 的异常处理系统
* cache\_t::init()
  * 缓存条件初始化
* \_imp\_implementationWithBlock\_init
  * 启动回调机制
* \_dyld\_objc\_notify\_register
  \*

## 二、 environ\_init

> 初始化环境变量

* 修改环境变量能够有效帮助我们进行开发调试

### 修改环境变量进行调试

如这样一个场景，项目代码较多，引用的SDK也很多，想要知道哪些类实现了 `+load` 方法该怎么办？

* 修改一个环境变量即可打印出来
  * 打开 `Edit Scheme` - `Run` - `Argument` 添加环境变量 `OBJC_PRINT_LOAD_METHODS = YES` 即可打印所有 `+load` 方法

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

效果：

```cpp
objc[58337]: LOAD: category 'NSObject(NSObject)' scheduled for +load
objc[58337]: LOAD: +[NSObject(NSObject) load]

objc[58337]: LOAD: category 'NSObject(NSObject)' scheduled for +load
objc[58337]: LOAD: +[NSObject(NSObject) load]

objc[58337]: LOAD: class 'NSColor' scheduled for +load
objc[58337]: LOAD: class 'NSApplication' scheduled for +load
objc[58337]: LOAD: class 'NSBinder' scheduled for +load
objc[58337]: LOAD: class 'NSColorSpaceColor' scheduled for +load
objc[58337]: LOAD: class 'NSNextStepFrame' scheduled for +load
objc[58337]: LOAD: +[NSColor load]

objc[58337]: LOAD: +[NSApplication load]

objc[58337]: LOAD: +[NSBinder load]

objc[58337]: LOAD: +[NSColorSpaceColor load]

objc[58337]: LOAD: +[NSNextStepFrame load]

objc[58337]: LOAD: category 'NSError(FPAdditions)' scheduled for +load
objc[58337]: LOAD: +[NSError(FPAdditions) load]

objc[58337]: LOAD: class '_DKEventQuery' scheduled for +load
objc[58337]: LOAD: +[_DKEventQuery load]

objc[58337]: LOAD: class 'RYModel' scheduled for +load
objc[58337]: LOAD: +[RYModel load]
```

自定义模型实现了`+load`方法被打印了出来，`+[RYModel load]`。

### 获取可用环境变量

在任意终端输入指令 `export OBJC_HELP=1` 即可获取环境变量列表，选择自己需要的进行使用，可以提高开发调试的效率哦。

```cpp
➜  RyukieDevGitBook git:(master) ✗ export OBJC_HELP=1
objc[57485]: Objective-C runtime debugging. Set variable=YES to enable.
objc[57485]: OBJC_HELP: describe available environment variables
objc[57485]: OBJC_PRINT_OPTIONS: list which options are set
objc[57485]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[57485]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[57485]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[57485]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
...
```

## 三、 static\_init

> 调用全局静态C++构造函数

* 执行全局静态 `C++` `构造函数`
* `libc` ***在 `dyld` 调用`构造函数`之前 调用*** `_objc_init()`&#x20;
* 所以我们要自己处理

```cpp
/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/

static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}
```

### 3.1 验证

***在我的代码中添加一个C++构造函数***

```cpp
__attribute__((constructor)) void ryFunc() {
    printf("我的constructor：%s \n", __func__);
}
```

* 发现并没有在这一步骤中执行我的C++构造函数

### 3.2 思考

这里其实调用的并非所有C++构造函数，而是在底层objc库中的构造函数。

***在objc源码中添加一个构造函数：***

```cpp
__attribute__((constructor)) void ryFuncInObjc() {
    printf("我的constructor：%s \n", __func__);
}
```

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

> 此处正常没有输出日志

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

***日志输出：***

```cpp
我的constructor：ryFuncInObjc
```

### 4.3 总结

* 这里调用的C++构造函数特指 `objc源码中定义的一系列构造函数`
* 因为全局构造函数非常重要，为了保证全局构造函数调用的及时性，所以这里自己进行了调用。

## 四、 runtime\_init

> runtime 运行时环境初始化

```cpp
void runtime_init(void)
{
    objc::unattachedCategories.init(32);
    objc::allocatedClasses.init();
}
```

通过查看这里的两个 `init` 方法我们发现，这两个是集合类型

```cpp
public:
    template <typename... Ts>
    void init(Ts &&... Args) {
        new (_storage) Type(std::forward<Ts>(Args)...);
    }

    Type &get() {
        return *reinterpret_cast<Type *>(_storage);
    }
};
```

### 4.1 unattachedCategories

```cpp
static UnattachedCategories unattachedCategories;

} // namespace objc
```

### 4.2 allocatedClasses

一个存储所有已经被 `allocated` 的类和元类的`表`

```cpp
/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/
namespace objc {
static ExplicitInitDenseSet<Class> allocatedClasses;
}
```

## 五、 exception\_init

初始化 `libobjc` 的异常处理系统

```cpp
/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
```

* 需要的话也可以自定义一异常捕捉回调

```cpp
FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);
```

## 六、 \_imp\_implementationWithBlock\_init

> 启动回调机制。通常不会做什么，因为所有的初始化都是懒加载的，但是对于某些进程，会迫不及待的加载 `trampolines dylib`。

## 七、 \_dyld\_objc\_notify\_register

> 这里进行了镜像文件的读取与加载，后面会单独进行分析
