场景
假设一个这样的场景,早高峰赶公交,没带公交卡,掏出手机打开App1准备扫码上车,结果App半天进不去,后面的人都怒视着你,然后果断打开App2,秒开,那么下一次你会选择哪个App呢,所以说App的启动速度不仅决定了用户体验,更是决定了它是否能过赢得更多客户
App启动时都做了啥呢
启动的方式
- 冷启动:从零开始启动App
- 热启动:App已经在内存中,后台存活着,用户重新启动进入App的过程,该过程做的事情非常少,启动很快
优化主要从冷启动的角度出发,主要分为main函数执行前和main函数执行后

main函数执行之前
-
dyld(全名
dynamic link editor
Apple的动态链接器,可以用来加载Mach-O
文件) 装载App的可执行文件,同时递归加载所有依赖的动态库 - 当dyld文件将可执行文件和动态库加载完毕后,通知
RunTime
进行下一步处理 -
RunTime
做的事情有
3.1. 调用map_images
进行可执行文件内容的解析和处理
3.2. 在load_images
中调用call_load_methods
调用类class
和分类category
的+load
方法
3.3. 进行各种objc
结构的初始化(注册objc
类,初始化类结构)
3.4. 调用C++静态初始化器和__attribute__((constructor))
修饰的函数
4.到此为止,可执行文件和动态库中所有的符号(Class , Protocol, Selector, IMP,...)都按格式加载到内存中被RunTime
管理
优化方案
- 减少动态库、合并一些动态库(定期清理不必要的动态库)
- 减少
Objc
类、分类的数量、减少Selector
数量(定期清理不必要的类分类)- 减少C++虚函数数量
- Swift尽量使用
struct
- 用
+ initialize
方法和dispatch_once
取代所有的__attribute__((constructor))
、C++静态编译器、Objc
的+load
方法
main函数执行之后
- 从
main()
函数执行开始,到appDelegate
的didFinishLaunchingWithOptions
方法里首屏渲染相关方法执行完成阶段
- 首屏初始化所需配置文件的读写操作;
- 首屏列表大数据的读取;
- 首屏渲染的大量计算等。
优化: 功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 启动必要的初始化功能,而哪些是只需要在对应功能开始使用时才需要初始化的。梳理完之后,将这些初始化功能分别放到合适的阶段进行
-
appDelegate
的didFinishLaunchingWithOptions
方法作用域内执行首屏渲染之后的所有方法执行完成阶段
- 非首屏其他业务服务模块的初始化、监听的注册、配置文件的读取等
- 第三方SDK初始化
优化:
main()
函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非首屏业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后去做.
将没必要的耗时方法滞后或者异步执行
启动时间获取
- 通过添加环境变量可以打印出App的启动时间(
Edit scheme
->Run
->Arguments
->Environment Variables
)DYLD_PRINT_STATISTICS
设置为1
Total pre-main time: 5.6 seconds (100.0%)
dylib loading time: 4.3 seconds (76.4%)
rebase/binding time: 1.1 seconds (20.3%)
ObjC setup time: 107.58 milliseconds (1.8%)
initializer time: 71.88 milliseconds (1.2%)
slowest intializers :
libSystem.B.dylib : 22.57 milliseconds (0.3%)
打印的是执行main函数之前的耗时
- Time Profiler定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时
- fishhook https://github.com/facebook/fishhook
-
戴铭的GCDFetchFeed 在需要检测耗时时间的地方调用
[SMCallTrace start]
,结束时调用stop
和save
就可以打印出方法的调用层级和耗时了。你还可以设置最大深度和最小耗时检测,来过滤不需要看到的信息
网友评论