前言
我们作为一个专业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
,就是有相关回调触发,这里我们还不得而知。