[iOS开发]App启动过程

1.1 动态库和静态库的认识

1.1.1 介绍

从本质上,库是一种可执行代码的二进制形式。可以被操作系统载入内存执行。库分为两种:静态库(.a .lib)和动态库(framework .so .dll)。
.a是纯二进制文件,.framework中除了有二进制文件还有资源文件,.a文件不能直接使用,至少需要.h文件的配合。而.framework可以直接使用。

.a + .h + sourceFile = .framework

所谓静态和动态是指链接过程,动态和静态是相对于编译器和运行期的。

静态库在程序编译时会被链接到目标代码中,程序运行时将不再需要载入静态库

动态库是在程序编译时会被链接到目标代码中,只是在程序运行时才被载入,因为在程序运行期间还需要动态库的存在

1.1.2 静态库

静态库即静态链接库(Windows 下的 .lib,Linux 和 Mac 下的 .a)。之所以叫做静态,是因为静态库在编译的时候会被直接拷贝一份,复制到目标程序里,这段代码在目标程序里就不会再改变了。

静态库的好处很明显,编译完成之后,库文件实际上就没有作用了。目标程序没有外部依赖,直接就可以运行。

当然其缺点也很明显,就是会使用目标程序的体积增大。

1.1.3 动态库

动态库在内存中只有一个,操作系统也只会加载一次到内存中。只是针对不同的进程进行各自的映射

代码段在内存中的权限都是只读的,所以多个程序虽然使用同一个动态库,但是并不会修改源代码

动态库即动态链接库(Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd)。与静态库相反,动态库在编译时并不会被拷贝到目标程序中,目标程序中只会存储指向动态库的引用。等到程序运行时,动态库才会被真正加载进来。

动态库的优点是,不需要拷贝到目标程序中,不会影响目标程序的体积,而且同一份库可以被多个程序使用(因为这个原因,动态库也被称作共享库)。同时,编译时才载入的特性,也可以让我们随时对库进行替换,而不需要重新编译代码。

动态库带来的问题主要是,动态载入会带来一部分性能损失,使用动态库也会使得程序依赖于外部环境。如果环境缺少动态库或者库的版本不正确,就会导致程序无法运行(Linux 下喜闻乐见的 lib not found 错误)。

1.2 Mach-O的简单认识

程序要想运行起来,其可执行文件格式就需要被操作系统所理解,对于iOS来说,Mach-O是其可执行文件的格式。

其是一种用于可执行文件、目标代码、动态库、内核转储的文件格式。

Mach-O有三种文件类型 Executable、Dylan、Bundle

Executable

Executable是app的二进制主文件,我们可以在Xcode项目中的products文件中找到它。
在这里插入图片描述

Dylib

Dylib是动态库,动态库分为动态连接库和动态加载库。
动态连接库:在没有被加载到内存的前提下,当可执行文件被加载,动态库也随之被加载到内存中,随着程序启动而启动。
动态加载库:当需要的时候再使用dlopen等通过代码或者命令的方式加载,程序启动后而加载。

Bundle

Bundle是一种特殊的Dylib,我们无法对其进行链接。所能做的就是在runtime运行时通过dlopen来加载它,它可以在macOS上用于插件

Image和Framework

Image(镜像文件)包含了上述的三种类型
Framework可以理解为动态库。

1.3 dyld的简单认识

dyld是动态链接器。

在 iOS / macOS 系统中,仅有很少的进程只需内核就可以完成加载,基本上所有的进程都是动态链接的

Mach-O镜像文件中会有很多对外部的库和符号的引用,但是这些引用并不能直接用,在启动时还必须要通过这些引用进行内容填充,这个填充工作就是由dyld来完成的。

对于链接生成的Mach-O可执行文件,在程序启动时通过dyld进行链接载入。

App启动过程主要研究的就是dyld的执行过程。

在这里插入图片描述

1.4 编译过程

在这里插入图片描述

  • 预处理:载入.h.m.cpp等文件进行预处理产生.i文件。也叫预编译,可以替换掉所有的宏;处理条件预编译指令,比如#if;删除所有注释;展开头文件。
  • 编译:将.i文件转换成汇编语言,产生.s文件。进行一些语法分析、词法分析、语义分析;还会进行一些优化,比如真值判断,假值判断。
  • 汇编:将汇编语言转换成机器可以执行的指令,产生.o文件。每一条汇编语言都对应一条机器指令。
  • 链接:对.o文件中引用的其他库的地方进行引用,生成可执行文件Mach-O。dyld就在此处起作用。

1.5 App启动过程

在这里插入图片描述
通过打印调用栈,我们清晰的看到App启动的起点是_dyld_start

查看一下dyld的源码

在dyldStartup的文件中我们找到如下
在这里插入图片描述

  1. 开始进行初始化链接载入等操作:call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)
  2. 清空栈和跳转到主程序的start(不重要):clean up stack and jump to "start" in main executable
  3. 开始执行main函数LC_MAIN case, set up stack for call to main()

看一下call dyldbootstrap::start(app_mh, argc, argv, dyld_mh, &startGlue)函数

我们本质就要进入这个文件的start函数进行查看
其核心就是执行了一个dyld::_main()函数

看一下dyld::main的源码主要做了什么事呢?

大体说明:

  1. 设置运行环境
  2. 调用instantiateFromLoadedImage函数加载可执行文件到内存中
  3. 调用link函数

详细步骤:

第一步: 环境变量配置。根据环境变量设置相应的值以及获取当前运行架构。我们如果自己人为设置环境变量,在此就可以被获取到。

第二步:检查是否开启了共享缓存。以及共享缓存是否映射到共享区域,例如UIKit\CoreFoundation等。苹果的动态库都放在了缓存里,叫动态库共享缓存,从iOS 3.1开始,为了提高性能,绝大部分的系统动态库文件都打包存放到了一个缓存文件中(dyld shared cache)

第三步:主程序的初始化。调用instantiateFromLoadedImage函数实例化了一个ImageLoader对象。

第四步:插入动态库。DYLD_INSERT_LIBRARIES环境变量,调用loadInsertedDylib加载。

第五步:链接主程序。

第六步:链接动态库。

第七步:弱符号绑定。

第八步:执行初始化方法。

第九步:寻找主程序入口,即main函数。

总结

  • 主要的就是动态库的插入和链接、主程序的初始化和链接、初始化方法
  • 该函数最终返回引用程序main函数的地址,最后Dyld去调用它。
  • 主程序的初始化只做了一件事,就是创建一个ImageLoader对象,后面就是通过这个对象来加载二进制文件到内存中。

总结

知识点总结:

  1. 源文件->预处理->编译->汇编->链接->可执行文件Mach-O
  2. dyld是动态链接器,它的作用时机在于App启动,将可执行文件Mach-O进行动态链接,链接动态库,进行主程序的加载以及方法的初始化。(类、分类、协议的加载,还有Cxx函数和load()的加载)
  3. App启动时dyld链接器用来加载程序
  4. 加载过程中重点在于load的加载和类、分类、协议、方法的加载
  5. load的加载和类、分类、协议、方法的加载都是在objc中的,但是他们的实际调用时机是在dyld中,因为是通过map_images和load_images回调函数的注册来做到的。

启动过程总结:

启动过程,会先有一个启动动画,为了防止一个应用占用过多的系统资源加载不出来启动动画,iOS有一个看门狗机制。如果我们的应用程序对一些特定的UI事件(比如启动、挂起、恢复、结束)响应不及时,Watchdog 会把我们的应用程序干掉,并生成一份响应的 crash 报告。错误编码是8badf00d
在这里插入图片描述
看门狗解决方案:

  1. 异步网络请求。看门狗问题很大程度会出现在同步网络调用而阻塞主线程的情况。
  2. 在非主线程中使用同步网络请求
  3. 通过 RunLoop 来操控一切,一旦超过既定的超时时间,就提示用户重试或者暂时先跳过网络请求。

App在启动时会先通过dyld动态链接器进行库文件(比如经典的runtime库或者UIKit)与汇编后的.o文件进行动态链接,以及进行主程序的加载,加载是通过objc库注册的map_images和load_images回调函数来进行加载类信息的。而在加载过程中会进行类、分类、协议的加载,load_images里执行了load方法。

最后 dyld 会调用 main() 函数。main() 会调用 UIApplicationMain(),程序启动。

UIApplication对象是应用程序的象征,每一个应用都有自己的UIApplication对象,而且是单例的。通过[UIApplication sharedApplication]可以获得这个单例对象,一个iOS程序启动后创建的第一个对象就是UIApplication对象。然后创建UIApplication的delegate对象 —–(您的)AppDelegate ,开启一个runloop,每当监听到对应的系统事件时,就会通知AppDelegate。

在这里插入图片描述

在这里插入图片描述
后面就是一个viewcontroller的生命周期了。差不多就这些。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值