一、冷启动
一般而言冷启动的过程为从点击图标到didFinishLaunching
执行完。这个过程分为两个阶段。
- 1、
main()
之前,操作系统加载App可执行文件Mach-O
到内存,然后执行加载与链接,然后执行到main()
。将此阶段定义为T1
- 2、
main()
之后,从main()
到didFinishLaunching
执行完毕。此阶段为T2
- 3、实际项目中用户真正看到内容还需要经历一段时间。即
didFinishLaunching
后首页渲染、数据请求、定位、等。将此阶段定义为T3
。
经过上述3个阶段。试一次从用户使用角度定义的冷启动,T1+T2+T3
。中每个阶段都可以进行优化,
二、分析且优化
1、T1
T1
的过程大致为:
- 加载dyld到App进程,
- 加载动态库(包括所依赖的缩影动态库)
- Rebase 内部指针调整。
- Bind 把指针正确的指向Image外部的内容。通过符号表查找
- 初始化Objective-C Runtime
- 其他初始化
mach-o
可执行文件再熟悉不过了。结构为
header
头部,包含了可执行的CPU结构,x86 arm64Load commands
加载命令,包含文件的组织结构和在虚拟内存的布局方式Data
数据,包含Load commands
中需要的各个段的数据。Section data
Section data
中包含了
__TEXT
代码段,只读,包括函数,和只读的字符串,__DATA
数据段,读写,包括可读写的全局变量等__LINKEDIT
包含方法金额变量的元数据。以及代码签名等。
方案、减少动态库,objc类、方法、Objc 的+load 在实际项目中T1优化的空间不是很大。+load
可以用+initialize
代替。 可以通过查看Mach-O
中
- __TEXT:__objc_methname: 中包含了代码中的所有方法
- __DATA__objc_selrefs 中包含了所有被使用的方法的引用
2、T2
项目中会在didFinishLaunching
中执行大量的启动任务。SDK的初始化。优化空间比较大。
需要瘦身didFinishLaunching
。我们通过梳理重新分类,把它们划分归类,哪些早执行,哪些后面执行。并且制定规则,后续迭代也可以遵守。
3启动项
创建一个启动项管理器,在适当的节点执行启动。要考虑代码简洁,方便阅读。启动项可以复用代码。可以通过在编译时把函数的指针,写入到可执行文件的__DATA 中,运行时再通过__DATA端取出来进行操作。解决了初始化的方法能够覆盖所以启动阶段。此方法参考QTEventBus来实现的。Clang中有section
方法
`__attribute__ ((used, section ("__DATA,__QTEventBus")))`的作用是告诉编译器这个结构体会用到,麻烦写到`__DATA`段中的`__QTEventBus` section里。
复制代码
4、T3
使用闪屏。缓存策略 闪屏
展示闪屏的时候构建首页UI。不浪费时间。
5、实际操作
使用objc_cover 来检测。Python脚本运行即可。 获取到了未使用的方法。很多很多。经过排查后优化了部分。 接下来进行启动项的重新优化。先看当前的时间 使用了BLStopwatch
来打印时间
对比。自己宏定义了一套监听时间的插件。
appDidFinishLuanch
里面进行拆分。项目使用的是QTEventBus
的QTAppModule
来进行初始化SDK。并且是异步调用。但是所以初始化都集中一起本身就不太合理。 一顿操作后优化到了2秒左右