[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvVsXYMt-1666076450602)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1cd17d6dcb294b99aea3ef1b9b29c735~tplv-k3u1fbpfcp-watermark.image)]

前言

本文将从底层原理出发,讲解iOS 应用加载流程

程序加载框架

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yJlpnZLF-1666076450603)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/55c9b61123a84b9ab7bb8f129e73c83a~tplv-k3u1fbpfcp-watermark.image)]

源文件通过预编译,将代码词法语法进行分析然后交给编译器编译之后生成一些汇编文件链接装载进应用内,最终变成可执行文件

动态库/静态

静态库: 链接时,会被完整的复制可执行文件内,会被系统多次使用拷贝多份;

动态库: 链接时不复制程序运行时由系统动态加载内存系统加载一次,多个程序共用,节省内存空间

静态库与动态库的区别,主要在链接时的区别一个静态链接,一个是动态链接;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jsq0AL1C-1666076450604)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/182e33c13c6c41cfb7f2b6bfed759350~tplv-k3u1fbpfcp-watermark.image)]

静态库/动态库生成可执行文件后,接下来验证执行文件
提示:尽量使用MacO工程验证,如果使用iOS工程,在终端运行执行文件的时候,会将模拟器或者是真机运行起来,这时候会需要一些访问权限处理起来比较麻烦;此处我使用源码工程验证)

运行源码工程生成可执行文件:

在这里插入图片描述

在 Finder 中,将可执行文件,拽入终端运行

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CJ1dYjQN-1666076450604)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8db0e17dc94c45b19b3bb3737e331cc7~tplv-k3u1fbpfcp-watermark.image)]

使用静态库或者动态库的优势:

了解了库的部分原理后,那么在应用中,库是如何加载到内存中呢???

dyld

  • dyld 链接器

是通过链接器:也就是dyld动态链接器,加载到内存中的;

App启动,会加载程序需要的库(如:libSystem库),进入runtime
运行时,注册回调函数(_dyld_objc_notify_register函数),然后加载新imageimage是库,是镜像文件,库加载的过程,就是一种映射过程,将库映射一份到内存中) ,image加载完毕,执行map_images、Load_images函数,这两个函数执行完毕后,才是main()函数执行; 这就是库的加载流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Nevy5wO-1666076450604)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ae957cd6fa514944a5781e5c03e5d661~tplv-k3u1fbpfcp-watermark.image)]

  • dyld原理

接下来我们着重查看调用main()函数之前,dyld的链接过程

运行工程,在main函数之前,先执行start流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0k2KtGg-1666076450605)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0239c6d0c0b245ac903944959d6506a0~tplv-k3u1fbpfcp-watermark.image)]

通过全局断点看看start流程是怎样的?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FoLCLY9g-1666076450605)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/846d9aa4953b4bd39ed1b2dbfa62e053~tplv-k3u1fbpfcp-watermark.image)]

start断点没有执行,但是我们发现,在调用main()函数之前,日志区打印load方法log,这就表示,load方法在main()函数之前就已经调用

既然load函数在main()之前,那么在load函数内断点查看底层执行流程,断点在load函数内,在log区通过bt打印栈队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JwOwLN5N-1666076450605)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/618328f97e8a45a198e0d4f7f592499d~tplv-k3u1fbpfcp-watermark.image)]

通过栈流程可以看到,最先执行的是_dyld_start,并且是在dyld源码内;

dyld源码中,搜索_dyld_start函数,我们发现_dyld_start函数,是采用汇编的形式,并且,根据不同的架构,采用不同的流程

汇编的源码,阅读起来不是很方便,但从注释中,我们阅读到_dyld_start函数,将会执行dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)函数,所以我们可以直接定位到这个函数;

由于这是C函数命名规范,可以解读为:在dyldbootstrap文件内,执行start函数;
如此,我们查询dyldbootstrap文件,再查询start函数;

start函数内,最终返回的是dyld::_main()函数,而dyld::_main函数源码复杂,难已阅读,需要开启上帝视角我们直接查看它的返回值,依据返回值,由下往上逆推;

dyld::_main函数返回的resultresult赋值方式,更多的是来自于sMainExecutable函数

// instantiate ImageLoader for main executable
sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath);

//instantiateFromLoadedImage:镜像文件加载器

加载镜像文件赋值给sMainExecutable;

// load any inserted libraries
**if** ( sEnv.DYLD_INSERT_LIBRARIES != **NULL** ) {
**for** (**const** **char*** **const*** lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != **NULL**; ++lib) 
loadInsertedDylib(*lib);
}

// record count of inserted libraries so that a flat search will look at 
// inserted libraries, then main, then others.
//获取动态库镜像文件数量
sInsertedDylibCount = sAllImages.size()-1;

获取镜像文件后,接下来开始链接;

//开始链接镜像文件
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, **true**, ImageLoader::RPathChain(**NULL**, **NULL**), -1);
sMainExecutable->setNeverUnloadRecursive();
if ( sMainExecutable->forceFlat() ) {
    gLinkContext.bindFlat = **true**;
    gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
    //判断动态库镜像文件是否存在
if ( sInsertedDylibCount > 0 ) {
    for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
        ImageLoader* image = sAllImages[i+1];
        //link 动态库镜像文件
        link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
        image->setNeverUnloadRecursive();
}
......

镜像文件链接结束,开始绑定程序;

sMainExecutable->weakBind(gLinkContext);
gLinkContext.linkingMainExecutable = false;
......

linkweakBind结束,主程序开始运行

  • initializeMainExecutable:运行主程序
initializeMainExecutable(); 

最后通知dyld,进入main()函数

notifyMonitoringDyldMain();

这就是dyld的大体流程内部有很多细节大家可以自行查看,在此就不一一展开讲述;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VE5Yt4TK-1666076450606)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/605ddf82b880431a93b408ea66ae7613~tplv-k3u1fbpfcp-watermark.image)]

initializeMainExecutable 程序运行

  • initializeMainExecutable源码:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HZoseQdq-1666076450606)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0d1523e0852b41cdbae58c57a0940523~tplv-k3u1fbpfcp-watermark.image)]

通过流程可以看到,初始化主程序系统会做一些准备,那么是什么准备呢,接下来我们看processInitializers()这个函数;

void ImageLoader::processInitializers(const LinkContext&amp; context, mach_port_t thisThread,
InitializerTimingList&amp; timingInfo, ImageLoader::UninitedUpwards&amp; images)
{
uint32_t maxImageCount = context.imageCount()+2;
ImageLoader::UninitedUpwards upsBuffer[maxImageCount];
ImageLoader::UninitedUpwards&amp; ups = upsBuffer[0];
ups.count = 0;
// Calling recursive init on all images in images list, building a new list of
// uninitialized upward dependencies.

//在当前线程,对images list 中的所有 image 调用递归init,建立一个新的images list
for (uintptr_t i=0; i < images.count; ++i) {
    images.imagesAndPaths[i].first->recursiveInitialization(context, thisThread, images.imagesAndPaths[i].second, timingInfo, ups);
}
// If any upward dependencies remain, init them.
//递归流程
if ( ups.count > 0 ) 
    processInitializers(context, thisThread, timingInfo, ups);
}

processInitializers()函数是一个递归的过程,并且init所有的image,那么调用recursiveInitialization函数,又是什么情况呢?这个流程真的是,越看越迷糊

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nuFhxn7d-1666076450607)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb1e180559774390b6be49534a3b8191~tplv-k3u1fbpfcp-watermark.image)]

  • recursiveInitialization函数

在这里插入图片描述

先加载依赖文件,再加载当前文件;这是因为依赖文件没有加载的话,当前文件是无法加载的;举例:
ViewA内有一个子ViewB,如果子ViewB没有加载完成的话,那么ViewA就没有办法引用ViewB,就导致ViewA无法完成;

recursiveInitialization函数中,加载文件后,系统都执行了notifySingle函数,接下来我们分析这个函数;

  • notifySingle函数

  • sNotifyObjCInit

initializeMainExecutable流程图
在这里插入图片描述

原文地址:https://blog.csdn.net/weixin_37767095/article/details/127388673

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_9305.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注