App
启动分为冷启动和热启动,我们说的启动优化一般是指冷启动优化。若要想优化,首先我们必须明确启动过程。
启动过程分为两个阶段
-
main
函数前 -
main
函数后
main
函数前
可以通过添加Xcode
环境变量DYLD_PRINT_STATISTICS
来打印pre-main
花费的时间
如果想查看更加详细的信息再添加环境变量DYLD_PRINT_STATISTICS_DETAILS
Total pre-main time: 5.0 seconds (100.0%)
dylib loading time: 3.6 seconds (72.2%)
rebase/binding time: 1.1 seconds (22.9%)
ObjC setup time: 100.24 milliseconds (1.9%)
initializer time: 139.23 milliseconds (2.7%)
slowest intializers :
libSystem.B.dylib : 5.24 milliseconds (0.1%)
执行流程

- 加载可执行文件
- 加载动态库由于使用了
ASLR
(Adress Space Layout Randomization
)rebase
[调整镜像内指针偏移]binding
[调整对镜像外符号信用偏移];一般来说动态库会加载100
-400
个大多数为OS dylib
(系统动态库),相对来说OS dylib
在加载时做了优化调整 - 初始化
runtime
:包括,类注册,属性动态调整,方法唯一性验证,分类中方法插入到类方法中 -
C++
静态变量初始化,构造函数attribute(constructor)
初始化
优化方案
- 减少动态库加载:苹果支持
6
个非系统动态库合并
合并动态库:
lipo -create path/yourFramework1 path/yourFramework2 -output path/yourFramework
合并静态库:
lipo -create '/sim/lib.a' '/dev/lib.a' -output 'lib.a'
-
减少
ObjC
类(class
),方法(selector
),分类(category
)的数量ObjC
因为支持了动态语言,内部会维护一个记录类名与类关系的表,如果类数量多就会造成表很大- 移除不用的类
// 安装fui工具,在终端中执行命令 官网:https://github.com/dblock/fui sudo gem install fui -n /usr/local/bin // 使用, 查找当前目录下未使用的类;注意查找出的内容并不一定靠谱,还需要自己在XCode中验证 fui find
// 使用otool命令可查看__DATA.__objc_classrefs段和__DATA.__objc_classlist段,两者的差集可以认为是定义了但未使用的类。 // 1. 获取可执行文件下所有OC类名 otool -v -s __DATA __objc_classlist 可执行文件名 // 2. 获取可执行文件所有引用到的OC类名 otool -v -s __DATA __objc_classrefs 可执行文件名 // 3. 两者的差集就是代码中没有直接使用到的OC类,不过需要注意反射机制使用类名来操作的情况,需要人工做下筛选 // 4. 获取所有符号,可以取出地址与符号对应关系 nm -nm 可执行文件名 // 5. 如果一个子类实例化,父类未实例化,那么父类不会出现在__objc_classrefs这个段里。需要将移除未使用的这部分OC类 // 可以获取到类的继承关系 otool -oV 可执行文件名 参考资料:https://juejin.im/post/5d5d1a92e51d45620923886a 参考脚本:https://github.com/xuezhulian/classunref
- 合并分类
- 将
load
方法中的内容推迟到initialize
中操作
放到initialize方法中,并适时使用dispatch_once来保证执行效果
- 减少
C++
虚函数的数量
创建虚函数表有开销;
- 减少
C/C++
中构造器,非基本类型的常量
C/C++的构造器函数(用attriubte((constructor))修饰的函数),和创建非基本类型的C++静态全局变量(通常是类或结构体)
- 使用
Swift struct
内部做了优化,符号数量更少
- 二进制重排,减少
page fault
产生
请求分页系统中每当要访问的页面不存在是,便会触发一个缺页中断(
page fault
),然后操作系统就会阻塞这个进程,,直到调页完成后,才会重新执行。由此如果发生缺页中断次数太多就会耗时较多。我们此处只考虑启动过程中
缺页中断视频讲解
https://www.bilibili.com/video/av86534144?p=6
重排的目的就是为了在启动过程中尽可能少的触发page fault。
查看当前App
启动产生多少次page fault
.
1. 通过Profile -> System Trace
2. 选择真机,选择工程,点击启动,当首个页面加载出来就停止
3. 选择主线程,-> 选择 Summary:Virtual Memory 其中File Backed Page In Count 即为pageFault次数
方案:
1. 构建一个orderFile.order;有序文件
2. 在Xcode Build settings中搜索Order File,配置orderfile路径
- 构建有序文件
本质是通过全局的
AOP
,Hook
所有方法
使用AppOrderFiles
工具进行操作
https://github.com/yulingtianxia/AppOrderFiles
详细理解方案
http://www.zyiz.net/tech/detail-127196.html
main函数后
执行流程
- 首屏初始化所需配置文件的读写操作
- 首屏列表数据的读取
- 首屏渲染的大量计算等
优化方案
梳理功能调整调用时机
a. 可以延迟加载的库延迟
b. 复杂耗时计算,延迟或者开线程
c. 首页控制器最好使用纯代码方式
常用策略
分析link map
文件
link map
是编译连接时生成的一个txt
文件,它生成的目的就是帮助程序员分析包大小。
link map
记录了每个方法在当前二进制架构下占据的空间。通过分析link map
,我们可以了解到每个类甚至每个方法占据了多少安装包空间。
使用方式如下
// 1. 开启
Xcode build setting 中开启Write Link Map
// 2. 配置Linkmap路径
path to link map
总结
大概阐述导致启动慢的原因,及解决方案;
针对比较深入的知识点,也查看了不少资料,作为切入理解的点。作为后续深入研究的方向。
参考及延伸:
https://www.jianshu.com/p/b19cd03eea68
http://yulingtianxia.com/blog/2019/09/01/App-Order-Files/
http://www.zyiz.net/tech/detail-127196.html
https://developer.apple.com/library/archive/documentation/Performance/Conceptual/CodeFootprint/Articles/ImprovingLocality.html#//apple_ref/doc/uid/20001862-117091-BCIBJEBH
网友评论