1、往期文章
iOS 底层探索文章系列
我们在上一篇文章中已经分析过 dyld 的加载流程,那么 dyld 与我们的 objc 之间是怎样关联起来的呢,这是本文我们分析的重点。
关于这一块的分析,我们先抛出两个经典的面试题,这在我们之后的几篇文章中会慢慢进行分析探讨。
1. APP启动,在
main函数之前都具体做了哪些内容? 2.load在什么时候进行调用?子类、父类以及分类load的调用顺序?
2、APP 编译流程及 dyld 启动流程

注意: 只有 静态库会在编译阶段打包到可执行文件中,动态库是在程序运行时才会被加入到可执行文件。如果对于它们之间的区别不怎么了解的同学,可以参考 iOS静态库与动态库的区别与打包
根据上面的编译流程图,我们可以得知,在应用程序启动前,会先对代码进行编译,在编译阶段会把静态库打包到可执行文件中,编译完成之后,就会进入启动阶段,涉及到 App 的启动流程,那么就肯定少不了我们的动态链接器 dyld,整个启动过程都由它来进行协调。

所以,我们可以把 App 的启动过程分为两个阶段,一个是 main 函数之前,另一个是 main 函数之后,我们先看一下 main 函数之前主要做了哪些操作
1. 调用
dyld对主程序运行环境初始化。 2. 生成imageLoader将动态库生成对应的image镜像文件,然后载入到内存中进行链接绑定。 3. 初始化所有动态库,在执行所有插入的动态库初始化的时候,同时对load_images进行了绑定。 4. 在执行初始化的过程中,会优先初始化系统库libSyatem,运行Runtime,这个过程会进入到Runtime的入口函数_objc_init中。 5. 把之前链接的动态库以及符号都交给Runtime进行map_images和load_images操作,然后Runtime执行完load_images之后会回调到dyld内部。 6. 当dyld收到信息回调后,最后会查找main函数的入口LC_MAIN,然后就会调用main函数。
3、_objc_init 源码分析
本文采用的是 objc4-781) 版本。
先从系统库 libSystem 的 Runtime 入口函数 _objc_init 开始分析:
/************************************************************************ _objc_init* Bootstrap initialization. Registers our image notifier with dyld.* Called by libSystem BEFORE library initialization time**********************************************************************/void _objc_init(void){static bool initialized = false;if (initialized) return;initialized = true;// fixme defer initialization until an objc-using image is found?// 读取影响运行时的环境变量。如果需要,还可以打印环境变量帮助(如添加 OBJC_PRINT_LOAD_METHODS、OBJC_DISABLE_NONPOINTER_ISA 等等)environ_init();// 关于线程 key 的绑定(比如每线程数据的析构函数)tls_init();// 运行 C++ 静态构造函数。在 dyld 调用我们的静态构造函数之前,libc 会调用 _objc_init(),因此我们必须自己做static_init();// runtime 运行时环境初始化,里面主要是unattachedCategories、allocatedClasses(分类初始化)runtime_init();// 初始化 libobjc 的异常处理系统exception_init();// 缓存条件初始化cache_init();// 启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载 trampolines.dylib。_imp_implementationWithBlock_init();// 注册回调函数// map_images: 当 dyld 将 image 镜像文件加载进内存时,会触发该函数。// load_images: 当 dyld 初始化 image 时,会触发该函数。// unmap_image: 当 dyld 移除掉 image 时,会触发该函数。_dyld_objc_notify_register(&map_images, load_images, unmap_image);#if __OBJC2__didCallDyldNotifyRegister = true;#endif}
3.1 environ_init: 环境变量初始化
下面贴出源码实现的关键代码部分
/************************************************************************ environ_init* Read environment variables that affect the runtime.* Also print environment variable help, if requested.**********************************************************************/void environ_init(void) {// Print OBJC_HELP and OBJC_PRINT_OPTIONS output.if (PrintHelp || PrintOptions) {for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {const option_t *opt = &Settings[i];if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);}}}
我们可以通过以下两种方式打印所以的环节变量
- 将某一个判断条件强行设置为 YES

- 通过终端命令
export OBJC_HELP=1输出环境变量

以上这些环境变量,可以在工程里面通过 target -> Edit Scheme -> Run -> Arguments -> Environment Variables 中配置。

3.2 tls_init: 线程 key 的绑定
void tls_init(void){#if SUPPORT_DIRECT_THREAD_KEYS// 本地线程池pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);#else// 析构函数_objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);#endif}
3.3 static_init: 运行系统级别的 C++ 静态构造函数
主要是运行系统级别的 C++ 静态构造函数,保证 系统级别的 C++ 静态构造函数比我们自定义的 C++ 函数先行调用
/************************************************************************ 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]();}}
3.4 runtime_init: 运行时环境初始化
主要是运行时的初始化,分为两个部分,分类的初始化 和 类的表的初始化。这一部分我们后面的文章再进行详细分析。
void runtime_init(void){objc::unattachedCategories.init(32);objc::allocatedClasses.init();}
3.5 exception_init: 初始化 libobjc 的异常处理系统
- 主要是初始化
libobjc的异常处理系统,注册异常处理的函数回调,实现对异常的监控处理。
/************************************************************************ exception_init* Initialize libobjc's exception handling system.* Called by map_images().**********************************************************************/void exception_init(void){old_terminate = std::set_terminate(&_objc_terminate);}
_objc_terminate函数内部会对OC的异常,调用foundation的uncaught_handler(如果设置了的话)
/************************************************************************ _objc_terminate* Custom std::terminate handler.** The uncaught exception callback is implemented as a std::terminate handler.* 1. Check if there's an active exception* 2. If so, check if it's an Objective-C exception* 3. If so, call our registered callback with the object.* 4. Finally, call the previous terminate handler.**********************************************************************/static void (*old_terminate)(void) = nil;static void _objc_terminate(void){if (PrintExceptions) {_objc_inform("EXCEPTIONS: terminating");}if (! __cxa_current_exception_type()) {// No current exception.(*old_terminate)();}else {// There is a current exception. Check if it's an objc exception.@try {__cxa_rethrow();} @catch (id e) {// It's an objc object. Call Foundation's handler, if any.(*uncaught_handler)((id)e);(*old_terminate)();} @catch (...) {// It's not an objc object. Continue to C++ terminate.(*old_terminate)();}}}
objc-exception中提供了读写uncaught_handler的方法
/************************************************************************ _objc_default_uncaught_exception_handler* Default uncaught exception handler. Expected to be overridden by Foundation.**********************************************************************/static void _objc_default_uncaught_exception_handler(id exception){}static objc_uncaught_exception_handler uncaught_handler = _objc_default_uncaught_exception_handler;/************************************************************************ objc_setUncaughtExceptionHandler* Set a handler for uncaught Objective-C exceptions.* Returns the previous handler.**********************************************************************/objc_uncaught_exception_handlerobjc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn){objc_uncaught_exception_handler result = uncaught_handler;uncaught_handler = fn;return result;}
可以看到
uncaught_handler就是一个函数指针。
3.6 cache_init: 缓存初始化
主要是缓存初始化
void cache_init(){#if HAVE_TASK_RESTARTABLE_RANGESmach_msg_type_number_t count = 0;kern_return_t kr;while (objc_restartableRanges[count].location) {count++;}kr = task_restartable_ranges_register(mach_task_self(),objc_restartableRanges, count);if (kr == KERN_SUCCESS) return;_objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",kr, mach_error_string(kr));#endif // HAVE_TASK_RESTARTABLE_RANGES}
3.7 _imp_implementationWithBlock_init: 启动回调机制
主要是启动回调机制。通常这不会做什么,因为所有的初始化都是惰性的,但是对于某些进程,我们会迫不及待地加载 trampolines.dylib。
/// Initialize the trampoline machinery. Normally this does nothing, as/// everything is initialized lazily, but for certain processes we eagerly load/// the trampolines dylib.void_imp_implementationWithBlock_init(void){#if TARGET_OS_OSX// Eagerly load libobjc-trampolines.dylib in certain processes. Some// programs (most notably QtWebEngineProcess used by older versions of// embedded Chromium) enable a highly restrictive sandbox profile which// blocks access to that dylib. If anything calls// imp_implementationWithBlock (as AppKit has started doing) then we'll// crash trying to load it. Loading it here sets it up before the sandbox// profile is enabled and blocks it.//// This fixes EA Origin (rdar://problem/50813789)// and Steam (rdar://problem/55286131)if (__progname &&(strcmp(__progname, "QtWebEngineProcess") == 0 ||strcmp(__progname, "Steam Helper") == 0)) {Trampolines.Initialize();}#endif}
3.8 _dyld_objc_notify_register: dyld 注册回调函数
我们在上一篇文章中已经详细分析过 dyld 的加载流程,我们看一下 _dyld_objc_notify_register 的源码声明部分
//// Note: only for use by objc runtime// Register handlers to be called when objc images are mapped, unmapped, and initialized.// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to// call dlopen() on them to keep them from being unloaded. During the call to _dyld_objc_notify_register(),// dyld will call the "mapped" function with already loaded objc images. During any later dlopen() call,// dyld will also call the "mapped" function. Dyld will call the "init" function when dyld would be called// initializers in that image. This is when objc calls any +load methods in that image.//void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,_dyld_objc_notify_init init,_dyld_objc_notify_unmapped unmapped);
从注释里面,我们就可以看出:
- 这个函数的调用是在
objc runtime的时候 - 注册在映射,取消映射和初始化
objc镜像时要调用的处理程序。 dyld将使用包含objc-image-info的镜像文件的数组回调mapped函数。
3.9 dyld 与 objc 的关联
我们通过源码来体现它们之间的关联关系
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,_dyld_objc_notify_init init,_dyld_objc_notify_unmapped unmapped){dyld::registerObjCNotifiers(mapped, init, unmapped);}
上面这处代码的调用之处是在 libobjc 的 _objc_init 函数中
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
所以我们能看到它们之间的一一对应关系
static _dyld_objc_notify_mapped sNotifyObjCMapped;static _dyld_objc_notify_init sNotifyObjCInit;static _dyld_objc_notify_unmapped sNotifyObjCUnmapped;
根据定义,结合我们上一节的分析,我们可以知道 load_images 也就是 sNotifyObjCInit 函数指针的调用时在 notifySingle 方法里面的

然后我们查找赋值 sNotifyObjCInit 的地方,可以找到是在 registerObjCNotifiers 函数中
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmapped){// record functions to callsNotifyObjCMapped = mapped;sNotifyObjCInit = init;sNotifyObjCUnmapped = unmapped;// 省略代码......}
继续查找 registerObjCNotifiers 调用的地方,最终找到了 _dyld_objc_notify_register
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,_dyld_objc_notify_init init,_dyld_objc_notify_unmapped unmapped){dyld::registerObjCNotifiers(mapped, init, unmapped);}
所以这里就可以得出 dyld 与 objc 直接的关联关系了,也就是
sNotifyObjCMapped==mapped==map_images;sNotifyObjCInit==init==load_images;sNotifyObjCUnmapped==unmapped==unmap_image;
3.10 map_images 的调用时机
接下来我们看看 map_images 的调用时机,也是一样的套路,我们搜索 sNotifyObjCMapped 函数指针是在哪里进行调用的

然后我们继续搜索 notifyBatchPartial 调用的地方,看到是在 registerObjCNotifiers 中进行调用的。

所以我们在这里可以得出
map_images是在registerObjCNotifiers中进行调用map_images比load_images先前调用。
