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

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

## 前言

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

![dyldinit01](/files/-MfBKqtugQjfvuyd-Wsb)

* 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](/files/-Meeo1Yal6k6ZcFVzpsl)

效果：

```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](/files/-Meeo1Yjj7cxDL7ovG40)

> 此处正常没有输出日志

![static\_init](/files/-Meeo1Ykx0fEEBrDNIG9)

***日志输出：***

```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

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


---

# 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/15.ios-ying-yong-qi-dong-er-objcinit.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.
