iOS总结-APP启动过程-main()函数执行前

APP启动的快慢,直接决定了用户的体验好坏,毕竟往往第一印象是真的很重要的!
APP启动分为三个阶段:main函数执行前,main函数执行后,首屏渲染完成后.
main启动前:
首先说下Mach-O文件
 Mach-O文件格式是OSX与iOS系统上的可执行文件
 
mach-o主要分为三部分:
Header头部,保存了一些基本信息,包含可以执行的CPU结构,比如x86,arm64 
LoadCommands:可以理解为加载命令,在加载Mach-O文件会使用的数据来确定内存的分布以及相关的加载命令.如main函数的加载地址,出现所需dyld的文件路径等.
Data:数据,包含load commands中需要各段segment的数据
我们通过MachOView来打开一个工程项目,可以看到所有可执行文件:

可以看到dyld的路径在LC_LOAD_DYLINKER,一般是在/usr/lib/dyld
LC_MAIN是main函数加载地址,还可以看到所使用的第三方库AFNetworking,FMDB
系统加载可执行文件后,通过分析dyld路径来加载dyld,然后交给dyld来继续表演.

dyld:(the dynamic link editor)动态拦截器,代码开源,源码基本上是C++
ImageLoader:辅助加载特定可执行文件格式的类,对应实例称为image(可执行文件,Framework库,bundle文件)
dyld主要负责初始化程序环境,将可执行文件及相应依赖库加载到内存生成相应的ImageLoader类的image(镜像文件)对象,对这些image进行链接,其中runtime就是在这个过程中被初始化,在dyld:_main方法里进行.
_main是dyld函数,并非我们程序里的main函数.
在项目配置环境DYLD_PRINT_ENV为1来打印

可以看到插入的库:libBacktraceRecording.dylib和libViewDebuggerSupport.dylib
有时候我们会在三方App的Mach-O通过修改DYLD_INSERT_LIBRARIES的值来加入我们自己的动态库,从而注入代码,hook别人的App(相关资料)
使用dyld2启动应用的过程
 
  1.系统先读取App的可执行文件(Mach-O文件)
  2.从里面获得dyld的路径,加载dyld,dyld去初始化运行环境,开启缓存策略,包括所依赖的所有动态库
     dyld会首先读取Mach-O文件的Header和load commands,接着知道可执行文件依赖的动态库.如加载动态库A到内存,接着检查A所依赖的动态库,就这样递归加载,知道所有动态库加载完毕.通常一个App所依赖的动态库在100-400个左右,大多数是系统的动态库,他们被缓存到dyld shared cache,这样读取效率高
  3. Rebase 4.Bind
   Rebase 修改内部(指向当前Mach-O文件)的指针指向
   Bind 修正外部指针指向
  为什么要用Rebase?两种技术安全:ASLR 和 Code Sign
 ASLR 即地址空间布局随机化,App被启动的时候,程序会被映射到逻辑的地址空间,这个逻辑的地址空间有一个起始地址,而ASLR技术使得这个其实地址是随机的.
 Code Sign是加密哈希不是针对整个文件,而是对于每一个Page,保证在dyld加载的时候,对于每一个page进行独立的验证.
 Mach-O采用PIC技术(Position Independ code),当你程序调用printf,会在__DATA段中建立一个指针指向printf,通过这些指针间接调用,dyld这时候需要做一些fix-up工作,来帮助应用程序找到这些符号的实际地址.
Rebase是因为ASLR使得地址随机化,Code Sign导致不能修改Image,Rebase只需要增加对应偏移量,待Rebase数据都在__LINKEDIT中.解决了内部的符号引用问题
Bind解决外部符号引用,根据字符串匹配的方式查找符号表
  5.初始化Objective-C Runtime
   Objective-C是动态语音,在执行main函数前,需要把类的信息注册到一个全局Table中,同时会把Category中的方法注册到对应的类中,同时唯一的Selector,类中方法会被覆盖.
  6.执行其他初始化代码
   +load方法
   C/C++静态初始化对象和标记为__attribute__(constructor)方法
  在swift中+load方法已被弃用,官方建议initialize.它们之间的区别
+load方法在main函数之前执行,+initialize在main函数之后执行
子类没有实现+load方法,不会调用父类+load方法,+load()方法一个耗时4毫秒,而子类没有实现+initizlize方法,会自动调用父类的+initialize方法
+load方法会在类被装进来的时候调用,+initialize第一次给某个类发送消息时调用(如实例化对象),且只会调用一次,懒加载模式
dyld3
    dyld2是纯粹的in-process,也就是在程序进程内执行的,意味着只有当应用程序被启动的时候,dyld2才开始执行
    dyld3是部分out-of-process,在App下载安装和版本更新的时候会执行;部分in-process做的事情:
    分析Mach-O Headers    分析依赖的动态库   查找需要Rebase & Bind之类的符号     把上述结果写入缓存
这样就在应用启动的时候,直接从缓存中读取数据,加快加载速度

main()函数执行前,主要的优化方式减少动态库加载,苹果最多可以支持6个非系统动态库合并一个.
 +load()方法放到首屏渲染后执行,每个+load()方法,4毫秒,使用+initialize()方法替换  
接下来就是main()函数执行后,也是我们优化启动速度最重要的阶段
参考:https://www.jianshu.com/p/43db6b0aab8e
        https://blog.csdn.net/Hello_Hwc/article/details/78317863

发布了36 篇原创文章 · 获赞 16 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_28551705/article/details/94634331