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_handler
objc_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_RANGES
mach_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 call
sNotifyObjCMapped = 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
先前调用。