在性能优化过程中,启动速度优化一直是一个比较大的点。Apple 官方期望 APP 的启动时间为 0.4 s,用户的期待时间为 2 s (来自调查)。不过作为开发者,当然希望我们的 APP 的启动速度能够尽可能的快。若是对 Time Profiler 不熟悉的同学可以先阅读一下先前的文章 Instruments 之 Time Profiler 使用。
冷启动和热启动
APP 启动分为冷启动(Cold Launches),当 APP 长时间没有被启动的时候,用户再次启动 APP 的时候就是冷启动,若是手机重启之后,APP 的第一次启动也是冷启动;冷启动对应的是热启动(Warm Launches),当 APP 启动时需要的 dylibs 仍然停留在设备的磁盘缓存的时候,这个时候就是热启动,热启动的速度会更快。
优化案例
使用 Xcode 版本为 8.3.2 ,设备为 iPhone 6 ,系统版本 10.3.1。 APP 每次启动之前需要重启一下手机,达到冷启动的效果。案例使用 raywenderlich 的
Catstagram 启动优化。该案例是一个带图片的列表。
优化 before main()
APP 启动优化可以分为 2 个部分,一个部分在 main() 函数之前,另一部分在 main()函数之后。对于 APP 的启动细节可以参考 WWDC 的 Optimizing App Startup Time 章节,本文主要讲使用 Time Profiler 来分析 APP ,然后根据分析结果来优化 APP,着重讲解 Time Profiler 的使用过程。
接下来,打开 Catstagram 案例,添加 Scheme 的 DYLD_PRINT_STATISTICS 参数,并设置值为 YES,见下图所示。该DYLD_PRINT_STATISTICS参数用于让 Xcode 控制台输出 APP 在 before main() 时机之前的花费时间。
设置好了之后,Command + R 在冷启动情况下启动 APP ,可以看到控制台的输出
Total pre-main time: 1.5 seconds (100.0%)
dylib loading time: 814.09 milliseconds (52.6%)
rebase/binding time: 52.20 milliseconds (3.3%)
ObjC setup time: 241.27 milliseconds (15.6%)
initializer time: 437.29 milliseconds (28.3%)
slowest intializers :
libSystem.B.dylib : 19.26 milliseconds (1.2%)
AsyncDisplayKit : 145.63 milliseconds (9.4%)
Catstagram : 277.81 milliseconds (17.9%)
重点关注 Total pre-main time: 1.5 seconds (100.0%) 的信息,冷启动情况在 pre-main 时机中可以看到信息 dylib loading time: 814.09 milliseconds (52.6%) ,也就是说我们的 dylib loading time 加载时间占据了 52.6% 。联想到我们的第三方库是采用 pod 管理,并且是 use_frameworks ,frameworks 是一个可优化点,
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'Catstagram' do
use_frameworks!
pod 'AFNetworking'
pod 'AsyncDisplayKit', '~> 2.2'
pod 'Yoga', '~> 1.3'
pod 'Firebase', '~> 3.15'
#pod 'FirebaseUI', '~> 3.1'
end
这个 frameworks 是一个可优化点,打开 Podfile,并注释掉 use_frameworks ,然后命令行执行 pod install 命令 ,更新工程,可以看到工程设置发生了变化。
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'Catstagram' do
#use_frameworks!
pod 'AFNetworking'
pod 'AsyncDisplayKit', '~> 2.2'
pod 'Yoga', '~> 1.3'
pod 'Firebase', '~> 3.15'
#pod 'FirebaseUI', '~> 3.1'
end
修改好了之后,Command + R 在冷启动情况下启动 APP ,查看控制台的输出。
Total pre-main time: 1.1 seconds (100.0%)
dylib loading time: 470.55 milliseconds (39.6%)
rebase/binding time: 31.07 milliseconds (2.6%)
ObjC setup time: 255.35 milliseconds (21.4%)
initializer time: 430.58 milliseconds (36.2%)
slowest intializers :
libSystem.B.dylib : 12.89 milliseconds (1.0%)
Catstagram : 792.04 milliseconds (66.6%)
从 log 中可以看到明显的变化,Total pre-main time 由之前 1.5 seconds 降到 1.1 seconds 。dylib loading time 由之前的 814.09 milliseconds (52.6%) 降到 470.55 milliseconds (39.6%) 。优化效果非常明显。
优化 after main()
优化完 before main() 之后,开始来优化 after main()。从图中可以看出启动优化的点是集中在 UIApplicationMain()上。
打开 Instruments 选择 Time Profiler 来分析 APP。
选择 APP 生命周期中的 Launching 生命周期来分析,如上图所示,可以清晰的看到耗时操作主要发生在 log 操作中,所以我们回到关于 log 的这段代码代码中,它可能是一个可以优化的点。
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
let appearance = UINavigationBar.appearance()
appearance.backgroundColor = .white
appearance.barTintColor = .white
CoolLogger.reportLogs()
return true
}
上述打 log 代码在 main 线程中执行,所以这段代码是可以优化的,我们将这段代码放到非 main 线程中执行。
DispatchQueue.global(qos: .background).async {
CoolLogger.reportLogs()
}
修改代码之后,重新启动手机,让 APP 进行冷启动,继续使用 Time Profiler 分析 APP,从下图的分析结果总可以看到优化取得了效果,APP 的 Laucning 生命周期没有出现 APP 的生命周期中,说明使用了及其短的时间来完成启动,这时间长度可以忽略。
总结
Time Profiler 可以看到代码的运行时长,配合它的Lift Cycle 工具可以用来优化 APP 的启动速度。 Time Profiler 只是一个工具,它只能帮助记录 APP 的运行状态,而开发者可以根据记录的状态分析 APP 的耗时操作,然后进行修改,再用 Time Profiler 验证。
参考
本文是 raywenderlich 的课程笔记,内容参考 Practical Instruments 课程
1、Demo 项目 https://files.betamax.raywenderlich.com/attachments/videos/786/0965b118-95eb-492f-804c-3135c7347130.zip
2、https://videos.raywenderlich.com/courses/74-practical-instruments/lessons/4