冷启动 与 热启动
- 热启动:如果你刚刚启动过App,这时候App的启动所需要的数据仍然在缓存中,再次启动的时候称为热启动。通常情况下热启动能帮助提升启动速度,但有时也可能会出现app卡死手动退出进程后重新打开仍然是卡死状态。
- 冷启动:如果是比较长时间没有启动过app或者设备刚刚重启,这种情况下启动App,就被称为冷启动。
查看启动时间
- 最佳速度:400ms,因为不添加任何同步任务从图标被点击到显示Launch Screen,然后Launch Screen消失这段时间就是400ms。如果app启动时间接近这个数值,那证明app的启动任务已经优化到最佳。
- 最慢速度:不可以大于20s,否则会被系统杀掉。
配置Xcode环境变量在日志中打印启动时间:
打开工程
-> Edit Scheme
-> Run
-> Environment Variables
根据需要添加DYLD_PRINT_STATISTICS
和DYLD_PRINT_STATISTICS_DETAILS
环境变量,1表示Yes,开启这个功能。
Total pre-main time: 433.19 milliseconds (100.0%)
dylib loading time: 341.79 milliseconds (78.8%)
rebase/binding time: 14.18 milliseconds (3.2%)
ObjC setup time: 35.27 milliseconds (8.1%)
initializer time: 41.79 milliseconds (9.6%)
slowest intializers :
libSystem.B.dylib : 3.40 milliseconds (0.7%)
libMainThreadChecker.dylib : 19.68 milliseconds (4.5%)
libViewDebuggerSupport.dylib : 8.75 milliseconds (2.0%)
优化启动
以main函数作为分水岭,启动时间其实包括了两部分:main函数之前和main函数到第一个界面的viewDidAppear:。
所以,优化也是从两个方面进行的,优化效果主要来自于后者,因为绝大多数App的瓶颈在自己的代码里。而对于pre-main的优化能做的无非是减少不必要的动态库引用、多个库合并成一个,从上面的打印数据也可以看出,主要耗时是在dylib loading
,消耗78.8%
的时间。
Main函数之后
从main函数开始执行,到第一个界面显示,期间一般做以下任务:
- 执行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions
- 初始化Window,初始化基础的ViewController结构(一般是UINavigationController+UITabViewController+多个UIViewController)
- 获取数据(Local DB/Network),展示给用户。
优化:
- 延迟初始化和加载不必要的UIViewController和View。
比方说UITabViewController有四个Item,在启动的时候尽量只初始化首页的页面,其它Item页面先用空VC占位。而且首页的内容中不必要的内容也可以先不初始化,做成懒加载形式,在用户确实需要查看和使用时再初始化。
- 对于确实需要启动时使用但又比较耗时的事物放倒后台处理,如果涉及到UI则在处理完成后把刷新任务放回主线程。
日志功能,日志往往涉及到DB操作;
文件读取,比如读取本地存储的省份城市区县文件和图片处理;
大量的计算,比如图片处理、比较大的json数据转Model;
- 能延迟初始化的尽量延迟初始化
三方SDK初始化,比如Crash统计、 像分享之类的,可以等到第一次调用再出初始化。
Main函数之前
Main函数之前是iOS系统的工作,所以这部分的优化往往更具有通用性。
Pre-Main包含以下工作:
- dylib loading time: 341.79 milliseconds (78.8%)
- rebase/binding time: 14.18 milliseconds (3.2%)
- ObjC setup time: 35.27 milliseconds (8.1%)
- initializer time: 41.79 milliseconds (9.6%)
- slowest intializers :
- libSystem.B.dylib : 3.40 milliseconds (0.7%)
- libMainThreadChecker.dylib : 19.68 milliseconds (4.5%)
- libViewDebuggerSupport.dylib : 8.75 milliseconds (2.0%)
优化:
-
loading dylib
:启动的第一步是加载动态库,加载系统的动态库使很快的,因为可以缓存,而加载内嵌的动态库速度较慢。所以,提高这一步的效率的关键是:减少动态库的数量。
- 合并动态库,比如公司内部由私有Pod建立了如下动态库:XXTableView, XXHUD, XXLabel,强烈建议合并成一个XXUIKit来提高加载速度。
-
rebase/binding & ObjC Runtime setup
:Rebase和Binding都是为了解决指针引用的问题。对于Objective C开发来说,主要的时间消耗在Class/Method的符号加载上,所以常见的优化方案是:
- 减少
__DATA
段中的指针数量。 - 合并Category和功能类似的类。比如:UIView+Frame,UIView+AutoLayout…合并为一个
- 删除无用的方法和类。
- 多用Swift Structs,因为Swfit Structs是静态分发的。可以参考Swift进阶之内存模型和方法调度
-
Initializers
:
用initialize替代load。不少同学喜欢用method-swizzling来实现AOP去做日志统计等内容,强烈建议改为在initialize进行初始化。
load在程序启动的时候就会调用,而且必须阻塞等着所有类的load方法都执行完;initialize在类首次使用的时候调用。减少
__atribute__((constructor))
的使用(__attribute__((constructor))
用是在main函数之前,执行一个函数,便于我们做一些准备工作)。