我们今天对app的加载流程进行探索一下,看看app在启动的过程中,具体都做了哪些事情。
首先我们来运行起一个项目,运行成功后,我们在Xcode上选择Product -> Show Build Folder in Finder,会跳到一个Build文件夹下,然后按照下图中的步骤找到一个跟应用程序同名的文件,显示包内容:
打开包内容后,找到一个黑色的exec文件,它是一个可执行文件,这个文件是mach-o格式的。
什么是mach-o格式?
mach-o是一种文件格式,比如可执行文件、目标文件(.o文件)、静态库、动态库等,这些都是mach-o格式的。
像我们平时写的.h和.m文件,在我们运行项目时,都是要经过以下几个过程,最终变成可执行文件:
1、预编译:这阶段一般是处理代码中以" # "开头的预编译命令,包括替换宏定义、根据"#import"命令把相应的文件插入到指定位置、删除注释等工作。
2、编译:这个阶段主要进行词法分析、语法分析、语义分析等,生成汇编代码文件。
3、汇编:将编译阶段生成的汇编代码文件,翻译成机器指令,生成.o文件。
4、链接:分为静态链接和动态链接。
对于步骤4,再详细说明一下:
我们写的.h和.m文件会生成一个.o文件,但是我们可能在A类中调用了B类中的变量或者函数,但是每个模块的.o文件都是单独生成的,它们不知道其它模块的.o文件里的内容,这时就需要链接器登场了,链接器把所有的.o文件进行解析和符号收集等,这就把所有的.o文件连在了一起,形成一个可执行文件,这个过程就是静态链接。
说到这儿,再说一下静态库,静态库就是一堆.o文件的集合,静态链接阶段所链接的所有.o文件就包括静态库里面的.o文件,所以静态链接结束后,静态库就不存在了。
动态库是一个已经链接完全的镜像image,其中包含可执行文件、dylib、bundle这几种形式。
下面,我们在项目的main方法中加一个断点,通过image list命令来看下动态库列表:
这些.dylib文件都是动态库。
dyld
我们的app在启动的时候,其实是从exec()方法开始的,在这个方法里,app对应的可执行文件被加载到内存,dyld也被加载到内存,之后dyld就开始了动态链接。
dyld是"the dynamic link editor"的缩写,它主要做哪些工作呢?
1、递归加载可执行文件中所有的动态库
2、rebase和binding
3、调起main函数
dyld的工作是在main函数执行之前,那我们通过断点查看一下。 在ViewController中添加一个+(void)load方法,运行,在load方法断住,通过bt命令查看栈信息:
看到,是从_dyld_start方法开始的,
之后执行dyldbootstrap::start方法,这是在dyldbootstrap命名空间下的start方法,在dyld的源码中搜一下,start方法的最后是去执行_main方法:
_main方法里最重要的一个方法是initializeMainExecutable(),它的注释是run all initializers,进行所有的初始化的操作。
// run all initializers
initializeMainExecutable();
查看initializeMainExecutable方法具体实现,先是初始化所有的动态库:
然后初始化可执行文件和我们写的那些类:
runInitializers方法里,其中红框内的这一行是关键:
processInitializers方法主要做两件事:
1、为初始化动态库做准备
2、初始化动态库
先看红框中的注释,意思是说,如果该动态库还依赖其它的动态库,那么被依赖的库也需要初始化一下,所以这是个递归的方法。
上面代码中的recursiveInitialization方法,就是具体执行初始化的。
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
recursive_lock lock_info(this_thread);
recursiveSpinLock(lock_info);
if ( fState < dyld_image_state_dependents_initialized-1 ) {
uint8_t oldState = fState;
// break cycles
fState = dyld_image_state_dependents_initialized-1;
try {
// initialize lower level libraries first
for(unsigned int i=0; i < libraryCount(); ++i) {
ImageLoader* dependentImage = libImage(i);
if ( dependentImage != NULL ) {
// don't try to initialize stuff "above" me yet
if ( libIsUpward(i) ) {
uninitUps.imagesAndPaths[uninitUps.count] = { dependentImage, libPath(i) };
uninitUps.count++;
}
else if ( dependentImage->fDepth >= fDepth ) {
dependentImage->recursiveInitialization(context, this_thread, libPath(i), timingInfo, uninitUps);
}
}
}
// record termination order
if ( this->needsTermination() )
context.terminationRecorder(this);
// let objc know we are about to initialize this image
uint64_t t1 = mach_absolute_time();
fState = dyld_image_state_dependents_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
// initialize this image
bool hasInitializers = this->doInitialization(context);
// let anyone know we finished initializing this image
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
if ( hasInitializers ) {
uint64_t t2 = mach_absolute_time();
timingInfo.addTime(this->getShortName(), t2-t1);
}
}
catch (const char* msg) {
// this image is not initialized
fState = oldState;
recursiveSpinUnLock();
throw;
}
}
recursiveSpinUnLock();
}
我们看到里面有这句注释:
// let objc know we are about to initialize this image
翻译:让objc知道我们准备去初始化这个库
再往下看注释,可知doInitialization就是去初始化动态库:
// initialize this image
this->doInitialization(context)
doImageInit和doModInitFunctions方法里面都有判断,如果libSystem库还没有被初始化的话,会报错“...that does not link with libSystem.dylib”,说明这个库是依赖libSystem库的。
继续,
// let anyone know we finished initializing this image
翻译:通知所有人我们已经完成了这个库的初始化
fState = dyld_image_state_initialized;
oldState = fState;
context.notifySingle(dyld_image_state_initialized, this, NULL);
notifySingle就是去通知的方法:
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
...
部分代码省略
...
//dyld_image_state_dependents_initialized
if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
uint64_t t0 = mach_absolute_time();
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT,
(uint64_t)image->machHeader(), 0, 0);
(*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
uint64_t t1 = mach_absolute_time();
uint64_t t2 = mach_absolute_time();
uint64_t timeInObjC = t1-t0;
uint64_t emptyTime = (t2-t1)*100;
if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
timingInfo->addTime(image->getShortName(), timeInObjC);
}
}
...
部分代码省略
...
}
我们看到上面方法传的第一个参数state是dyld_image_state_dependents_initialized。
在这个状态下,会调sNotifyObjCInit方法,而sNotifyObjCInit方法是在registerObjCNotifiers方法中被赋值的,继续往上找,到_dyld_objc_notify_register方法,_dyld_objc_notify_register是在objc源码中的_objc_init方法中被调用的,可见,库的初始化依赖objc。
我们再来看下_objc_init方法是什么时候被调用的。给_objc_init加断点,bt一下:
看到,_objc_init是在libdispatch库中调用的,而libdispatch_init方法是在libSystem.B.dylib中的libSystem_initializer中调用的。
所以,我们可知,加载动态库,要先初始化libSystem,然后初始化libdispatch,然后再初始化libobjc。
我们再回头看_dyld_objc_notify_register有三个参数,第一个参数做为一个方法是在notifyBatchPartial方法里被赋值的,notifyBatchPartial在notifyBatch方法里调用的,而notifyBatch方法其实在上面的runInitializers方法里我们就已经展示过了,就在processInitializers方法下面一行:
所以,map_image和load_image的执行时机,都是在动态库完成初始化的时候。至于这两个方法具体做了哪些工作,我们将在后面的类加载文章中来讲解。
第三个参数unmap_image会被赋值给sNotifyObjCUnmapped方法,这个方法会在removeImage方法中调用,即库被删除的时候。
总结一下:
1、dyld初始化动态库时,会走到recursiveInitialization方法,这个方法会调用doInitialization方法去初始化具体的库,等库初始化完成,就去通知和记录。同时,初始化库的过程是个递归的过程,在初始化某个库的时候,如果发现其还依赖其它的库,那么依赖的库也要去初始化。
2、最先被初始化的是libSystem库,然后是libdispatch库,然后再初始化libobjc库。