【iOS应用启动(二)】环境配置与runtime初始化

933 阅读4分钟

Hi 👋

我的个人项目扫雷Elic 无尽天梯梦见账本
类型游戏财务
AppStoreElicUmemi

本文基于 dyld-832.7.3objc4-818.2 源码

前言

结合上一文【iOS应用启动(一)】dyld与main函数及符号断点我们找到了 _objc_init 的调用栈

dyldinit01.png

  • 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 调用
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

1.1 源码分析

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 方法

objcinit01.png

效果:

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 即可获取环境变量列表,选择自己需要的进行使用,可以提高开发调试的效率哦。

➜  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
...

三、 static_init

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

  • 执行全局静态 C++ 构造函数
  • libc dyld 调用构造函数之前 调用 _objc_init()
  • 所以我们要自己处理
/***********************************************************************
* 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++构造函数

__attribute__((constructor)) void ryFunc() {
    printf("我的constructor:%s \n", __func__);
}
  • 发现并没有在这一步骤中执行我的C++构造函数

3.2 思考

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

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

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

objcinit02.png

此处正常没有输出日志

objcinit03.png

此处输出了日志

我的constructor:ryFuncInObjc

3.3 总结

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

四、 runtime_init

runtime 运行时环境初始化

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

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

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

static UnattachedCategories unattachedCategories;

} // namespace objc

4.2 allocatedClasses

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

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

五、 exception_init

初始化 libobjc 的异常处理系统

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
  • 需要的话也可以自定义一异常捕捉回调
FOUNDATION_EXPORT void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler * _Nullable);

六、 _imp_implementationWithBlock_init

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

七、 _dyld_objc_notify_register

这里进行了镜像文件的读取与加载,移步【iOS应用启动(三)】镜像文件的读取与加载详细了解。