分析工具:
xCode自带工具 Instrument 的 Timer Profiler。
测试设备:
- iPhone6 iOS 12.4.8,这台设备相对来说比较老旧,我们App在次设备上启动速度非常慢,所以效果较明显。
- iphone7 iOS 13.4
- iPhone11 iOS 14.0.1
测试方式,从点击录制开始启动App,到App启动页面结束点击停止,观察主线程的耗时。
测试启动结果
iPhone6 iOS 12.4.8测试结果:
可以看到主线程的耗时一共是6.04秒,start
占用5.71s,_dyld_start
占用327ms(这里需要注意,也因为不是第一次启动,所以dyld
二次加载存在直接从缓存中加载,所以耗时比第一次启动会短一些)。
iphone7 iOS 13.4 测试结果:
主线程的耗时一共是1.57秒,start
占用1.50s,_dyld_start
占用70ms
iPhone11 iOS 14.0.1 测试结果:
主线程的耗时一共是754ms,start
占用642ms,_dyld_start
占用111ms
我们看到最长的时间是iphone6的iOS12系统,6秒的启动时间是我们绝对接受不了的,下面我们看一下在main
函数中都是有那些方法调研占用了大多数的时间。
首先我们选择左下脚的 Call Tree 按钮,选择隐藏系统方法,方便我们查看我们自己的方法。
隐藏系统方能后,只剩下我们代码的方法列表和耗时,但是发现这些方法都是地址,并不是实际解析之后的方法名,如图:
这里需要注意一下需要DSYM解析成对应的方法名,需要修改一下项目buildSetting的设置,在对应的编译模式下,选择 DWARF with DYSM File
:
如果启动过程中有组件的方法,组件通过cocoapod集成的,需要在对应的组件也设置上,可以通过遍历全局设置:
post_install do |installer|
installer.pod_target_subprojects.flat_map { |p| p.targets }.each do |t|
t.build_configurations.each do |config|
# 设置debug模式,其他模式类似
config.build_settings['DEBUG_INFORMATION_FORMAT'] = 'dwarf-with-dsym'
end
end
end
设置后需要执行一下 pod install
,重新把项目run到手机,通过 Time Profiler查看代码耗时:
可以看到,在启动过程中 有三个计较耗时的方法,启动 HBHRouterManager.regisgerRootVC(vc:)
方法耗时达到5.71秒,我们项目这个方法是路由注册处理,调用了全局的类列表并组成对应的Scheme链接映射表,所以比较耗时。所以我们要根据实际情况优化我们耗时的方法,或者调优,或者延后调用,不要堵塞主线程的启动过程。尤其是在 Appdelegate
的 application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
方法中 在return
之前,千万不要做特别耗时的工作堵塞主线程。可以尝试在此方法 return
之前 sleep(10)
,那么你的App启动肯定在10秒以上。
我们采用简单方式先处理一下这个耗时的方法:
// 子线程异步执行注册路由方法,不要堵塞主线程的启动过程
DispatchQueue.global().async {
HBHRouterManager.shared.regisgerRootVC(vc: rootViewController)
}
重新Run,看一下启动耗时:
可以看到main
和start
调用时间直接降低到了 436ms,App在iPhone6上启动时间从原来的6秒左右降低到739ms。
再看一下剩下的两台机器:
iphone7 iOS 13.4 测试结果:
主线程的耗时一共是339ms秒,start
占用256ms,_dyld_start
占用72ms
iPhone11 iOS 14.0.1 测试结果:
主线程的耗时一共是146ms,start
占用38ms,_dyld_start
占用108ms
以此类推,其他耗时的方法采用对应的优化方案调优,即可实现最简单的启动速度的优化。
这里只是简单介绍在主线程启动执行时的耗时方法进行优化,不同的启动场景对App的耗时影响也不太一样,苹果给我们例举了几种启动场景:
- 重启手机,首次启动App。
- 强制退出App,然后启动。
- 打开其他App,然后启动你的App。
- 使用一个非常大的App(例如,可以使用许多图形资源或实时摄像机输入的App),然后启动您的应用程序。
还列举了其他影响启动速度的因素:
App动态库的数量,直接影响到dyld加载的时间。
-
静态库初始化时:
-
C++
静态构造函数。 - 在类或者分类中的
+load
方法。 - 标有clang属性
__attribute__((constructor))
的方法 。 - 链接到App或Framework任何函数的
__DATA
、__mod_init_func
。
-
关于线上版本App的启动时长统计,苹果也给提供了工具,可以直接查看不通版本的启动耗时:
Xcode -> Window-> Organizer -> Metrics - > Launch Time
最后附上官网优化文档:苹果官网启动优化文档