APP启动过程及优化

2018-10-18  本文已影响0人  我是本人yyp

以下内容仅仅作为向大佬们学习中的总结和记录

一、名词解释

1.什么是image
无论对于系统的动态链接库还是对于App本身的可执行文件而言,他们都算是image(镜像),而每个App都是以image(镜像)为单位进行加载的,image包括以下文件:
<1>executable可执行文件 比如.o文件
<2>dylib 动态链接库
<3>bundle 资源文件

2.什么是ImageLoader
image 表示一个二进制文件(可执行文件或 so 文件),里面是被编译过的符号、代码等,而 ImageLoader 作用是将这些文件加载进内存,且每一个文件对应一个ImageLoader实例来负责加载。

3.Mach-O指哪些文件
Mach是一个操作系统内核,在Mach上,一种可执行文件的格式是Mach-O。苹果的大部分文件格式都是Mach-O格式。在iOS中,以下文件指的是Mach-O:
<1>Executable 可执行文件
<2>Dylib 动态库
<3>Bundle
<4>Image
<5>Framework

Mach-o结构如图:


image.png

可以通过命令行otool -l XXX来看看Mach-o内部信息


image.png
Header头部 ,包含可以执行的CPU架构, 比如Mac的 PPC, PPC64, IA-32, x86-64,iOS的arm系列。
magic,是mach-o文件的魔数,0xfeedface代表的是32位,0xfeedfacf代表64位

cputype和cupsubtype代表的是cpu的类型和其子类型
filetype,文件类型
ncmds 指的是加载命令(load commands)的数量
sizeofcmds 表示load commands的总字节大小

Load commands 加载命令,包含文件的组织架构和在虚拟内存中的布局方式。
cmd 是load command的类型,LC_SEGMENT的含义是(将文件中的段映射到进程地址空间)
cmdsize 代表load command的大小
segname 16字节的段名字,当前是__PAGEZERO
vmaddr 段的虚拟内存起始地址
vmsize 段的虚拟内存大小
.fileoff 段在文件中的偏移量
filesize 段在文件中的大小
maxprot,initprot 最高内存保护和初始内存保护
nsects段中包含section的数量


image.png

Data,可以拥有多个段(segment),每个段可以拥有零个或多个区域(section)。每一个段(segment)都拥有一段虚拟地址映射到进程的地址空间。大部分包含以下三个段:__TEXT 代码段,只读,包括函数,和只读的字符串
__DATA 数据段,读写,包括可读写的全局变量等
__LINKEDIT 包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。
sectname 第一个是__text ,就是主程序代码
segname 该section所属的 segment名
addr 该section在内存的启始位置
size 该section的大小
offset 该section的文件偏移
align 字节大小对齐
reloff 重定位入口的文件偏移
nreloc 需要重定位的入口数量

二、启动过程上

APP启动分为热启动和冷启动
启动时间在小于400ms是最佳的,因为从点击图标到显示Launch Screen,到Launch Screen消失这段时间是400ms。启动时间不可以大于20s,否则会被系统杀掉。

t(App总启动时间) = t1(main()之前的加载时间) + t2(main()之后的加载时间)

t1 =自身App可执行文件(.o文件的集合)的加载和系统dylib(动态链接库)。动态链接库包括:iOS 中用到的所有系统 framework,加载OC runtime方法的libobjc,系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。

具体加载顺序是先加载可执行文件,然后加载dyld,是个加载动态链接库的库。dyld从可执行文件的依赖库开始加载,递归加载所有的依赖库

如何加载动态库?

在Xcode中,可以通过设置环境变量来查看App的启动时间,DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS

<1>load dylibs

dyld会首先读取mach-o文件的Header和load commands。 �接着就知道了这个可执行文件依赖的动态库。例如加载动态库A到内存,接着检查A所依赖的动态库,就这样的递归加载,直到所有的动态库加载完毕。通常一个App所依赖的动态库在100-400个左右,其中大多数都是系统的动态库,它们会被缓存到dyld shared cache,这样读取的效率会很高。

针对这一步骤的优化有:
减少非系统库的依赖
合并非系统库
使用静态资源,比如把代码加入主程序

<2>Rebase&Bind

为什么要rebase?

有两种主要的技术来保证应用的安全:ASLR和Code Sign。

ASLR的全称是Address space layout randomization,翻译过来就是“地址空间布局随机化”。App被启动的时候,程序会被映射到逻辑的地址空间(CPU的虚拟地址,从物理地址映射到虚拟地址),这个逻辑的地址空间有一个起始地址,而ASLR技术使得这个起始地址是随机的。如果是固定的,那么黑客很容易就可以由起始地址+偏移量找到函数的地址。

在进行Code sign的时候,加密不是针对于整个文件,而是针对于每一个Page的。dyld进行加载的时候,是对每一个page进行独立的验证。

之所以要rebase是因为刚刚提到的ASLR使得地址随机化,导致起始地址不固定,另外由于Code Sign,导致不能直接修改Image。Rebase的时候只需要增加对应的偏移量即可。待Rebase的数据都存放在__LINKEDIT中。

bind指向的是镜像外部的资源指针。
rebase步骤先进行,需要把镜像读入内存,并以page为单位进行加密验证,保证不会被篡改,所以这一步的瓶颈在IO。bind在其后进行,由于要查询符号表,来指向跨镜像的资源,加上在rebase阶段,镜像已被读入和加密验证,所以这一步的瓶颈在于CPU计算。

优化该阶段的关键在于减少__DATA segment中的指针数量。我们可以优化的点有:
减少Objc类数量, 减少selector数量
减少C++虚函数数量
转而使用swift stuct(其实本质上就是为了减少符号的数量)

Objc setup

Objective C是动态语言,所以在执行main函数之前,需要把类的信息注册到一个全局的Table中。同时,Objective C支持Category,在初始化的时候,也会把Category中的方法注册到对应的类中,同时会唯一Selector。

initializers

以上三步属于静态调整(fix-up),都是在修改__DATA segment中的内容,而这里则开始动态调整,开始在堆和栈中写入内容。 在这里的工作有:
Objc的+load()函数
C/C++静态初始化对象和标记为attribute(constructor)的方法

总结一下:对于main()调用之前的耗时我们可以优化的点有:
减少不必要的framework,因为动态链接比较耗时
check framework应当设为optional和required,如果该framework在当前App支持的所有iOS系统版本都存在,那么就设为required,否则就设为optional,因为optional会有些额外的检查
合并或者删减一些OC类,关于清理项目中没用到的类,使用工具AppCode代码检查功能,查到当前项目中没有用到的类如下:
删减一些无用的静态变量
删减没有被调用到或者已经废弃的方法
将不必须在+load方法中做的事情延迟到+initialize中
尽量不要用C++虚函数(创建虚函数表有开销)

三、启动过程下

t2 = main方法执行之后到AppDelegate类中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法执行结束前这段时间。
主要是构建第一个界面,并完成渲染展示。
大部分项目会在该方法里初始化第一个页面,那么第一个页面的viewdidload时间就会算t2的时间中

大部分情况下我们都会把界面的初始化过程放在viewDidLoad,但是这个过程会影响消耗启动的时间。特别是在类似TabBarController这种会嵌套childViewController的ViewController的情况,它也会把部分children也初始化,因此各种viewDidLoad会递归的进行。

主要优化方法
尽量减少初始页面viewdidload方法中的业务

四、优化总结

1.移除不需要的动态库
2.移除不需要的类
一个叫做fui(Find Unused Imports)的开源项目能很好的分析出不再使用的类.https://github.com/dblock/fui
3.合并功能类似的类和扩展
由于Category和ObjC的动态绑定有很强的关系,所以实际上分类是比较占用启动时间的。尽量合并一些分类,会对启动有一定的优化作用。
4.压缩图片
5.优化applicationdidfinishlaunching
6.优化rootviewcontroller加载

参考文章
https://blog.csdn.net/Tencent_Bugly/article/details/77363817?locationNum=1&fps=1
https://blog.csdn.net/u011452278/article/details/54966682

上一篇下一篇

猜你喜欢

热点阅读