今天我们研究的是应用程序的加载过程,先做一下准备工作。
新建一个iphone工程
,添加下面代码:
ViewController.m
+ (void)load{
NSLog(@"%s",__func__);
}
main.m
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
NSLog(@"333333");
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
__attribute__((constructor)) void lyFunc(){
printf("来了 : %s \n",__func__);
}
运行查看这三个打印的顺序:
+[ViewController load]
来了 : lyFunc
333333
程序加载过程这些方法的调用顺序是:
load ——> C++ ——> main
main
函数作为程序的入口,为什确实最后执行的,我们需要研究的是在main
函数之前,程序到底做了什么?
程序编译过程
预编译
:预编译又称为预处理,主要做些代码文本的替换工作,处理#开头的指令。比如拷贝#include、#import包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做预备工作的阶段,主要处理#开始的预编译指令。生成.i文件
编译
:将高级语言转换为机器能识别的汇编语言。生成.s文件
汇编
:将汇编文件转换成机器码文件。生成.o文件
链接
:对.o文件中引用的其他的库进行引入,生成可执行文件。
动态库 和 静态库
-
静态库
:在链接阶段,将可汇编生成目标程序与它所引用的库一起链接打包到可执行文件中。此时静态库就不会改变了,静态库是直接拷贝,复制到目标程序。
-
优点
:编译完成,库文件就没有作用了,运行时可以直接使用 -
缺点
:不同的静态库中引用了相同的文件,如上图中的B、D,他就会拷贝两份相同的库文件,导致目标程序体积增大
,对性能、内存会有一定的影响。
-
-
动态库
:编译时相同的库并不会拷贝到目标程序,而是在目标程序载入的时候,把那些相同的库用一份共享实例加载进来。-
优点
:-
减少打包的体积
:共享内存,节约资源 -
更新动态库可以直接更新程序
:由于运行时才载入的特性,因此可以随时对下层库进行更换,而我们的代码却不用改变
-
-
缺点
:动态载入会带来一部分性能损失,如果当前环境缺少动态库,或者版本不正确,会导致程序无法运行
-
dyld加载流程
dyld是苹果的动态连接器
,加载到程序中的动态库,主要靠dyld
来链接管理。
下面我们通过源码分析dyld的加载流程,首先下载一份dyld源码,这篇文章使用的是750.6
。
拿到了源码,那么我们从哪里开始分析呢?借助一下上面的工程,在load
方法处加上断点,打印堆栈信息:
bt
我们可以看到堆栈的第一个信息是
_dyld_start
,也可以直接在左边窗口查看。
- 打开dyld源码,搜索
_dyld_start
找到__arm64__
环境下的源码
往下查找发现它调起了dyldbootstrap::start
方法
- 搜索
dyldbootstrap
找到start
方法
uintptr_t start(const dyld3::MachOLoaded* appsMachHeader, int argc, const char* argv[],
const dyld3::MachOLoaded* dyldsMachHeader, uintptr_t* startGlue)
{
// Emit kdebug tracepoint to indicate dyld bootstrap has started <rdar://46878536>
dyld3::kdebug_trace_dyld_marker(DBG_DYLD_TIMING_BOOTSTRAP_START, 0, 0, 0, 0);
// if kernel had to slide dyld, we need to fix up load sensitive locations
// we have to do this before using any global variables
rebaseDyld(dyldsMachHeader);
// kernel sets up env pointer to be just past end of agv array
const char** envp = &argv[argc+1];
// kernel sets up apple pointer to be just past end of envp array
const char** apple = envp;
while(*apple != NULL) { ++apple; }
++apple;
// set up random value for stack canary
__guard_setup(apple);
#if DYLD_INITIALIZER_SUPPORT
// run all C++ initializers inside dyld
runDyldInitializers(argc, argv, envp, apple);
#endif
// now that we are done bootstrapping dyld, call dyld's main
uintptr_t appsSlide = appsMachHeader->getSlide();
return dyld::_main((macho_header*)appsMachHeader, appsSlide, argc, argv, envp, apple, startGlue);
}
其核心就是dyld::_main
,返回调用了dyld
的main
函数。从左边的堆栈,也可以清晰的看到。其中macho_header
就是Mach-o
的头部。
- 点击跳转到
dyld::_main
函数
我们发现这个main
函数有600多行代码,在这里就不详细解释,感兴趣的同学可以自行研究。_main
函数主要做了以下事情:
1.环境变量配置
2.共享缓存:(UIKit、CoreFoundation等)
3.主程序的初始化
4.加入动态库
5.link主程序
6.link动态库
7.绑定弱引用
8.initialize初始化
9.main()
-环境变量配置
-
共享缓存
-
主程序初始化
-
加载动态库
-
link主程序
-
link动态库
-
弱引用绑定主程序
-
run initialize初始化方法
-
进入main()函数
initialize
我们主要分析第八步,看看run all initializers里面做了什么?
-
点击进入
initializeMainExecutable
源码
-
点击
runInitializers
主要是其中的processInitializers
方法 -
点击进入
processInitializers
-
进入
recursiveInitialization
源码
重点查看notifySingle
和doInitialization
方法
notifySingle
-
进入
notifySingle
源码
根据传入参数dyld_image_state_dependents_initialized
和dyld_image_state_initialized
找到下段代码
-
sNotifyObjCInit
直接搜索sNotifyObjCInit
没有找到相应的源码,只找到一段赋值代码。我们继续跟registerObjCNotifiers
流程
-
搜索
_dyld_objc_notify_register
只找到一处调用的地方,接着我们搜索查看_dyld_objc_notify_register
,并没有发现调用的地方。_dyld_objc_notify_register
实际上是在objc
源码中调用的.
-
在
objc-781
源码中搜索_dyld_objc_notify_register
在_objc_init
中找到,从这可知赋给sNotifyObjCInit
的值其实就是load_images
-
接着查看
load_images
源码
主要放大是call_load_methods
,调用所有+load
方法 点击
call_load_methods
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
//遍历所有的类,调用 + load 方法
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
//加载所有的category
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
看到这里想必大家都已经明白了,sNotifyObjCInit
实际上调用的是libobjc
里的load_images
方法,作用是调用所有类的+load
方法和加载category
。
doInitialization
- 点击进入
doInitialization
这里的doImageInit
和doModInitFunctions
这两个方法是关键。我们先看doImageInit
。
-
点击进入
doImageInit
这里主要进行动态库的初始化工作,值得注意的是libSystem.dylib
必须第一个初始化。 -
点击进入
doModInitFunctions
这里主要是加载C++
方法,我们可以在上面的demo
中打一个断点进行验证。
C++方法调用之前,确实调用了doModInitFunctions
至此我们整个的runInitializers
执行完毕了。大致流程是initializeMainExecutable ——> runInitializers ——> processInitializers ——> processInitializers ——> recursiveInitialization ——> notifySingle ——> load_images ——> call_load_methods ——> call_class_loads ——> call_category_loads ——> doInitialization ——> doImageInit ——> doModInitFunctions ——> main()
不知道大家有没有注意到notifySingle
调用load_images
时,必须先由_objc_init ——> _dyld_objc_notify_register ——> registerObjCNotifiers
中才给了notifySingle
赋值load_images
这个赋值必须要在notifySingle
调用之前,那么_objc_init
在什么时候调用了呢?这个我们并没有找到。
探索_objc_init调用时机
我们在objc-781源码中_objc_init
方法打上断点。查看堆栈情况
我们发现在
_objc_init
之前,调用了libSystem_initializer
和 libdispatch_initializer
方法。接下来我们要分别查看
libSystem
和libdispatch
的源码。开源库地址我下载的是Libsystem-1281.100.1
和libdispatch-1173.100.2
libSystem
- 打开
libSystem
源码,搜索libSystem_initializer
static void
libSystem_initializer(int argc,
const char* argv[],
const char* envp[],
const char* apple[],
const struct ProgramVars* vars)
{
_libSystem_ktrace0(ARIADNE_LIFECYCLE_libsystem_init | DBG_FUNC_START);
__libkernel_init(&libkernel_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(KERNEL);
__libplatform_init(NULL, envp, apple, vars);
_libSystem_ktrace_init_func(PLATFORM);
__pthread_init(&libpthread_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(PTHREAD);
_libc_initializer(&libc_funcs, envp, apple, vars);
_libSystem_ktrace_init_func(LIBC);
// TODO: Move __malloc_init before __libc_init after breaking malloc's upward link to Libc
__malloc_init(apple);
_libSystem_ktrace_init_func(MALLOC);
#if TARGET_OS_OSX
/* <rdar://problem/9664631> */
__keymgr_initializer();
_libSystem_ktrace_init_func(KEYMGR);
#endif
// No ASan interceptors are invoked before this point. ASan is normally initialized via the malloc interceptor:
// _dyld_initializer() -> tlv_load_notification -> wrap_malloc -> ASanInitInternal
_dyld_initializer();
_libSystem_ktrace_init_func(DYLD);
libdispatch_init();
_libSystem_ktrace_init_func(LIBDISPATCH);
....已省略部分代码.....
}
发现在其中调用了__malloc_init、 _dyld_initializer、 libdispatch_init
。
libdispatch
- 打开
libdispatch
源码,搜索libdispatch_init
void
libdispatch_init(void)
{
...已省略部分代码....
dispatch_assert(sizeof(struct dispatch_apply_s) <=
DISPATCH_CONTINUATION_SIZE);
if (_dispatch_getenv_bool("LIBDISPATCH_STRICT", false)) {
_dispatch_mode |= DISPATCH_MODE_STRICT;
}
#if DISPATCH_USE_THREAD_LOCAL_STORAGE
_dispatch_thread_key_create(&__dispatch_tsd_key, _libdispatch_tsd_cleanup);
#else
_dispatch_thread_key_create(&dispatch_priority_key, NULL);
_dispatch_thread_key_create(&dispatch_r2k_key, NULL);
_dispatch_thread_key_create(&dispatch_queue_key, _dispatch_queue_cleanup);
_dispatch_thread_key_create(&dispatch_frame_key, _dispatch_frame_cleanup);
_dispatch_thread_key_create(&dispatch_cache_key, _dispatch_cache_cleanup);
_dispatch_thread_key_create(&dispatch_context_key, _dispatch_context_cleanup);
_dispatch_thread_key_create(&dispatch_pthread_root_queue_observer_hooks_key,
NULL);
_dispatch_thread_key_create(&dispatch_basepri_key, NULL);
#if DISPATCH_INTROSPECTION
_dispatch_thread_key_create(&dispatch_introspection_key , NULL);
#elif DISPATCH_PERF_MON
_dispatch_thread_key_create(&dispatch_bcounter_key, NULL);
#endif
_dispatch_thread_key_create(&dispatch_wlh_key, _dispatch_wlh_cleanup);
_dispatch_thread_key_create(&dispatch_voucher_key, _voucher_thread_cleanup);
_dispatch_thread_key_create(&dispatch_deferred_items_key,
_dispatch_deferred_items_cleanup);
#endif
#if DISPATCH_USE_RESOLVERS // rdar://problem/8541707
_dispatch_main_q.do_targetq = _dispatch_get_default_queue(true);
#endif
_dispatch_queue_set_current(&_dispatch_main_q);
_dispatch_queue_set_bound_thread(&_dispatch_main_q);
#if DISPATCH_USE_PTHREAD_ATFORK
(void)dispatch_assume_zero(pthread_atfork(dispatch_atfork_prepare,
dispatch_atfork_parent, dispatch_atfork_child));
#endif
_dispatch_hw_config_init();
_dispatch_time_init();
_dispatch_vtable_init();
_os_object_init();
_voucher_init();
_dispatch_introspection_init();
}
最后我们看到libdispatch
调用了_os_object_init
。
- 继续搜索
_os_object_init
果然_objc_init
就在其中,所有的猜想都得到了验证。