ios应用程序的加载原理

前言

may.png 我们作为一个专业ios开发者,在忙着写繁杂的业务代码的同时也需要对app加载原理有个清楚的了解,这样才能对app作出更好的优化,以及写出更好性能的app,今天我们来分析下app的加载原理。

动态库与静态库

一般情况下,我们的应用程序加载的过程中都会依赖很多底层的库,比如ios中常见的UIKitFoundation等,这些库本质上是一些可执行的二进制文件,能够被操作系统加载到内存。库有两种形式,分为静态库和动态库,静态库一般常见的就是.a、.lib后缀的文件,动态库一般是.dll、.framework等后缀格式的文件。

    动态库与静态库的不同在于链接的不同,我们的app一般编译过程像这样的:

截屏2022-04-30 下午3.46.21.png

dyld——动态链接器

静态库会有重复加载的问题,消耗性能,而动态库则可以共享当前内存,节约内存。那么这些库是通过dyld(动态链接器——链接动静态库)加载进内存中的。应用程序加载过程如下图:

截屏2022-04-30 下午4.19.36.png

上图中runtime注册回调函数在objc源码可以看到相关函数_dyld_objc_notify_register(&map_images,load_images,unmap_image)如下图:

截屏2022-04-30 下午4.23.10.png 而流程中讲到的加载images也不是指图片,而是镜像文件,是库等相关文件映射到内存的一份替身。

dyld流程分析

我们接下来通过dyld源码的方式分析下它的流程,我们一般的工程项目其实可以看到它的入口:

截屏2022-04-30 下午5.06.30.png

接下来我们打开dyld源码搜下这个函数:

截屏2022-04-30 下午5.13.55.png

我们看到一个c++的命名空间dyldbootstrap,然后找start方法:

截屏2022-04-30 下午5.17.06.png

接着看一个关键的main方法:

截屏2022-05-02 上午9.04.06.png

整个main函数代码接近1000行,我们直接从返回值result看一些关键的信息:

截屏2022-05-02 上午9.06.48.png

我们可以看到,几个关键给result赋值的地方,都涉及到sMainExecutable,我们看下它链接主程序相关的方法instantiateFromLoadedImage

截屏2022-05-02 上午9.25.18.png

进入instantiateFromLoadedImage方法,看下instantiateMainExecutable

截屏2022-05-02 上午9.26.48.png 进入sniffLoadCommands看下相关实现:

截屏2022-05-02 上午9.32.55.png

我们在sniffLoadCommands方法中看到一些熟悉的名称,load_command、segment_command等,看下图:

截屏2022-05-02 上午9.35.24.png

我们再看下Mach-O文件中的相关信息,两者很相似,sniffLoadCommands方法中基本是按照Mach-O的表格式进行加载处理的:

截屏2022-05-02 上午9.40.07.png

我们再返回main方法看下相关的方法,instantiateFromLoadedImage之后,有个插入动态库的方法调用:

截屏2022-05-02 上午9.56.23.png

我们看一张dyld流程分析图,这样更清晰:

截屏2022-05-02 上午10.01.15.png

initializeMainExecutable —— Run all initializers

我们看下initializeMainExecutable方法的实现:

截屏2022-05-02 上午10.21.46.png

首先获得所有镜像文件的个数,然后进行循环调用runInitializers初始化:

截屏2022-05-02 上午10.25.57.png

接着我们进入runInitializers中一个关键的函数processInitializers,会看到一个递归初始化函数recursiveInitialization

截屏2022-05-02 上午10.40.35.png 从上图中,我们可以到几个关键函数,我们就看下notifySingle方法的实现:

截屏2022-05-02 上午10.44.47.png

找到一个路径和镜像文件加载的函数NotifyObjcInit,我们看它被赋值的地方:

截屏2022-05-02 上午10.50.40.png

上图中可以看到,它是在registerObjcNofiers中被赋值的,类型是_dyld_objc_nofify_init。接着就要找registerObjcNofiers被调用的地方,全局搜索:

截屏2022-05-02 上午10.54.24.png

最终找到是在_dyld_objc_notify_register方法调用的,这个方法我们很熟悉了,在libobjc.dylib中我们有看到的,那么一切似乎就串起来了:

截屏2022-05-02 上午10.59.15.png

objc_init反向推导到dyld

我们前面分析了dyld的一些关键流程,最终看到了libobjc.dylib中的_dyld_objc_notify_register,知道最终它会调用_objc_init,那么他们如何关联起来呢,我们这里取objc源码打印堆栈信息看下:

截屏2022-05-02 上午11.33.37.png

我们看到前面流程跟我们上面分析的一样的:_dyld_start ——> dyldbootstrap::start ——> _main ——> initializeMainExecutable() ——> runInitializers ——> processInitializers ——> recursiveInitialization,我们现在从_objc_init反向推导,看看中间的流程,我们先看下libdispatch.dylib(需要下载libdispatch源码)中_os_object_init是否调用了_objc_init

截屏2022-05-02 下午1.16.46.png

说明_os_object_init ——> _objc_init 是正确的,看堆栈信息上一步是libdispatch_init,我们看内部是否调用了_os_object_init

截屏2022-05-02 下午1.20.03.png

看到libdispatch_init内部有调用_os_object_init,所以libdispatch_init ——> _os_object_init ——> _objc_init也没问题。再看libdispatch_init的上一步是libSystem.B.dylib libSystem_initializer,这个我们要进libSystem.dylib源码看下:

截屏2022-05-02 下午1.25.31.png

可以看到libSystem_initializer中有libdispatch_init的调用,所以libSystem_initializer(libSystem.dylib) ——> libdispatch_init ——> _os_object_init ——> _objc_init也没有问题。接下来libSystem_initializer上一步就又回到了dyld源码中,我们找下doModInitFunctions

截屏2022-05-02 下午1.39.40.png

可以看到也有libSystem_initializer调用,所以调用流程变成:doModInitFunctions ——>libSystem_initializer(libSystem.dylib) ——> libdispatch_init ——> _os_object_init ——> _objc_init,接着我们看doModInitFunctions的上一步doInitialization

截屏2022-05-02 下午2.11.27.png

可以看到doInitialization中有调用doModInitFunctions,所以调用流程:doInitialization ——> doModInitFunctions(dyld) ——>libSystem_initializer(libSystem.dylib) ——> libdispatch_init ——> _os_object_init ——> _objc_init。而doInitializationrecursiveInitialization中调用的,我们看下图:

截屏2022-05-02 下午2.15.15.png

所以整个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,下图也可以看到这个清晰的流程:

截屏2022-05-02 下午2.21.21.png

其实这里还有一个关键问题,_dyld_objc_notify_register(&map_images, load_images, unmap_image)这里有一些参数赋值,如下图:

截屏2022-05-02 下午2.37.33.png

sNotifyObjCMapped这些被赋值的函数在什么时候被调用呢,因为在他们调用后做完相关的处理的时候,需要告知notifySingle,即下图中初始化完成的标识dyld_image_state_dependents_initialized,就是有相关回调触发,这里我们还不得而知。

截屏2022-05-02 下午3.12.38.png

猜你喜欢

转载自juejin.im/post/7093034058145333256