前言
我们作为一个专业ios开发者,在忙着写繁杂的业务代码的同时也需要对app加载原理有个清楚的了解,这样才能对app作出更好的优化,以及写出更好性能的app,今天我们来分析下app的加载原理。
动态库与静态库
一般情况下,我们的应用程序加载的过程中都会依赖很多底层的库,比如ios中常见的UIKit、Foundation等,这些库本质上是一些可执行的二进制文件,能够被操作系统加载到内存。库有两种形式,分为静态库和动态库,静态库一般常见的就是.a、.lib后缀的文件,动态库一般是.dll、.framework等后缀格式的文件。
动态库与静态库的不同在于链接的不同,我们的app一般编译过程像这样的:
dyld——动态链接器
静态库会有重复加载的问题,消耗性能,而动态库则可以共享当前内存,节约内存。那么这些库是通过dyld(动态链接器——链接动静态库)加载进内存中的。应用程序加载过程如下图:
上图中runtime注册回调函数在objc源码可以看到相关函数_dyld_objc_notify_register(&map_images,load_images,unmap_image)如下图:
而流程中讲到的加载
images也不是指图片,而是镜像文件,是库等相关文件映射到内存的一份替身。
dyld流程分析
我们接下来通过dyld源码的方式分析下它的流程,我们一般的工程项目其实可以看到它的入口:
接下来我们打开dyld源码搜下这个函数:
我们看到一个c++的命名空间dyldbootstrap,然后找start方法:
接着看一个关键的main方法:
整个main函数代码接近1000行,我们直接从返回值result看一些关键的信息:
我们可以看到,几个关键给result赋值的地方,都涉及到sMainExecutable,我们看下它链接主程序相关的方法instantiateFromLoadedImage:
进入instantiateFromLoadedImage方法,看下instantiateMainExecutable:
进入
sniffLoadCommands看下相关实现:
我们在sniffLoadCommands方法中看到一些熟悉的名称,load_command、segment_command等,看下图:
我们再看下Mach-O文件中的相关信息,两者很相似,sniffLoadCommands方法中基本是按照Mach-O的表格式进行加载处理的:
我们再返回main方法看下相关的方法,instantiateFromLoadedImage之后,有个插入动态库的方法调用:
我们看一张dyld流程分析图,这样更清晰:
initializeMainExecutable —— Run all initializers
我们看下initializeMainExecutable方法的实现:
首先获得所有镜像文件的个数,然后进行循环调用runInitializers初始化:
接着我们进入runInitializers中一个关键的函数processInitializers,会看到一个递归初始化函数recursiveInitialization:
从上图中,我们可以到几个关键函数,我们就看下
notifySingle方法的实现:
找到一个路径和镜像文件加载的函数NotifyObjcInit,我们看它被赋值的地方:
上图中可以看到,它是在registerObjcNofiers中被赋值的,类型是_dyld_objc_nofify_init。接着就要找registerObjcNofiers被调用的地方,全局搜索:
最终找到是在_dyld_objc_notify_register方法调用的,这个方法我们很熟悉了,在libobjc.dylib中我们有看到的,那么一切似乎就串起来了:
objc_init反向推导到dyld
我们前面分析了dyld的一些关键流程,最终看到了libobjc.dylib中的_dyld_objc_notify_register,知道最终它会调用_objc_init,那么他们如何关联起来呢,我们这里取objc源码打印堆栈信息看下:
我们看到前面流程跟我们上面分析的一样的:_dyld_start ——> dyldbootstrap::start ——> _main ——> initializeMainExecutable() ——> runInitializers ——> processInitializers ——> recursiveInitialization,我们现在从_objc_init反向推导,看看中间的流程,我们先看下libdispatch.dylib(需要下载libdispatch源码)中_os_object_init是否调用了_objc_init:
说明_os_object_init ——> _objc_init 是正确的,看堆栈信息上一步是libdispatch_init,我们看内部是否调用了_os_object_init:
看到libdispatch_init内部有调用_os_object_init,所以libdispatch_init ——> _os_object_init ——> _objc_init也没问题。再看libdispatch_init的上一步是libSystem.B.dylib libSystem_initializer,这个我们要进libSystem.dylib源码看下:
可以看到libSystem_initializer中有libdispatch_init的调用,所以libSystem_initializer(libSystem.dylib) ——> libdispatch_init ——> _os_object_init ——> _objc_init也没有问题。接下来libSystem_initializer上一步就又回到了dyld源码中,我们找下doModInitFunctions:
可以看到也有libSystem_initializer调用,所以调用流程变成:doModInitFunctions ——>libSystem_initializer(libSystem.dylib) ——> libdispatch_init ——> _os_object_init ——> _objc_init,接着我们看doModInitFunctions的上一步doInitialization:
可以看到doInitialization中有调用doModInitFunctions,所以调用流程:doInitialization ——> doModInitFunctions(dyld) ——>libSystem_initializer(libSystem.dylib) ——> libdispatch_init ——> _os_object_init ——> _objc_init。而doInitialization在recursiveInitialization中调用的,我们看下图:
所以整个dyld到_objc_init的流程就完整了:_dyld_start ——> dyldbootstrap::start ——> _main ——> initializeMainExecutable() ——> runInitializers ——> processInitializers ——> recursiveInitialization ——> doInitialization ——> doModInitFunctions(dyld) ——>libSystem_initializer(libSystem.dylib) ——> libdispatch_init ——> _os_object_init ——> _objc_init,下图也可以看到这个清晰的流程:
其实这里还有一个关键问题,_dyld_objc_notify_register(&map_images, load_images, unmap_image)这里有一些参数赋值,如下图:
sNotifyObjCMapped这些被赋值的函数在什么时候被调用呢,因为在他们调用后做完相关的处理的时候,需要告知notifySingle,即下图中初始化完成的标识dyld_image_state_dependents_initialized,就是有相关回调触发,这里我们还不得而知。