一.app冷热启动
APP热启动({
app退出后台,但进程还在系统里;
})
APP冷启动({
APP进程被Kill,重新启动,系统分配一个进程启动APP;
APP启动迟钝,查看主线程是否执行了大文件读写操作、在渲染周期中执行了大量计算等;
})
二.查看APP启动耗时情况
App开始启动后,系统首先加载可执行文件(自身App的所有.o文件的集合),然后加载动态链接器dyld,dyld是一个专门用来加载动态链接库的库。 执行从dyld开始,dyld从可执行文件的依赖开始, 递归加载所有的依赖动态链接库。
动态链接库包括:iOS 中用到的所有系统 framework,加载OC runtime方法的libobjc,系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。
查看方法如下:
Xcode菜单Product-Scheme-edit Scheme...0-Run-Arguments-Environment Variables,添加 DYLD_PRINT_STATISTICS
环境变量,value为1,编译并运行项目输出日志:
Total pre-main time: 539.46 milliseconds (100.0%)//main()总的启动时间
dylib loading time: 232.89 milliseconds (43.1%)//加载动态库占用
rebase/binding time: 32.56 milliseconds (6.0%)//指针重定位占用
ObjC setup time: 22.18 milliseconds (4.1%)//ObjC类初始化占用
initializer time: 251.68 milliseconds (46.6%)//各种初始化占用
slowest intializers :
libSystem.B.dylib : 5.12 milliseconds (0.9%)//intializers初始化最耗时1
libMainThreadChecker.dylib : 29.74 milliseconds (5.5%)//intializers初始化最耗时2
Template : 382.83 milliseconds (70.9%)//intializers初始化最耗时3
三.查看APP启动阶段({
main()执行前;({
- 【解析Info.plist】:加载信息,例如闪屏;沙盒建立、权限检查
- 【Mach-O加载】:加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法);加载可执行文件(App 的.o 文件的集合)
- 【加载动态链接库】:进行 rebase 指针调整和 bind 符号绑定;定位内部、外部指针引用,例如字符串、函数等
- 【Objc 运行时的初始处理】:包括 Objc 相关类的注册、category 注册、selector 唯一性检查等
- 【初始化】:执行 +load() 方法,执行声明为attribute((constructor))的C函数,C++静态对象加载
})
程序执行;({
- 调用
main()
- 调用
UIApplicationMain()
- 调用
applicationWillFinishLaunching
- ===优化:
- 【减少动态库加载】:每个库本身都有依赖关系,使用更少的动态库,并且建议在使用动态库的数量较多时,尽量将多个动态库进行合并。最多可以支持 6 个非系统动态库合并为一个。
- 【减少+load方法】:方法里的内容可以放到首屏渲染完成后再执行,或使用 +initialize() 方法替换掉。在一个 +load() 方法里,进行运行时方法替换操作会带来 4 毫秒的消耗。
- 【减少使用】:减少写attribute((constructor))的C函数,控制 C++ 全局变量的数量
})
main()执行后;({
从main()
函数开始至 appDelegate
的didFinishLaunchingWithOptions
结束,称为main()函数之后的部分。
主要执行内容
- 首屏初始化所需配置文件的读写操作;
- 首屏列表大数据的读取;
- 首屏渲染的大量计算等。
耗时的影响因素
- 执行
main()
函数的耗时 - 执行
applicationWillFinishLaunching
的耗时 rootViewController
及其childViewController
的加载、view
及其subviews
的加载
})
首屏渲染完成后;({
[首屏渲染完成之后]指的是非首屏其他业务服务模块的初始化、监听的注册、配置文件的读取等。
该阶段指的就是截止到 didFinishLaunchingWithOptions
方法作用域内执行首屏渲染之后的所有方法执行完成。从渲染完成时开始,到 didFinishLaunchingWithOptions
方法作用域结束时结束。
})
优化思路一:功能启动优化
main() 函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非首屏业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后去做。
根据刚需分置阶段进行
根据启动流程把刚需功能放置在启动阶段,其他业务功能放在合适的阶段
- 首屏渲染必要的初始化功能
- App 启动必要的初始化功能
- 只需要在对应功能开始使用时才需要初始化的功能
- 例如:主视图第一时间加载,里面的数据和界面延后加载
优化思路二: 方法启动优化
检查首屏渲染完成前主线程上的耗时方法,将非刚需的耗时方法滞后或异步执行。耗时较长的方法主要发生在计算大量数据的情况下,例如加载、编辑、存储图片和文件等资源。
+load() 方法,一个耗时 4 毫秒,100 个就是 400 毫秒,不可小视
优化三:移除不必要的动态库
移除项目中非必要的动态库
优化四:移除不必要用到的类
代码工程的维护非常重要
优化五:合并功能相似的类和扩展(Category)
由于Category的实现原理,和ObjC的动态绑定有很强的关系,实际上类的扩展是比较占用启动时间的。尽量可能合并一些扩展,并不是让你不使用扩展
优化六:压缩资源图片
图片小了,IO操作量小了,启动就快了。推荐 TinyPNG
优化七:优化applicationWillFinishLaunching
需要在applicationWillFinishLaunching
里处理的业务较多时,可以管理起这些任务
将不需要马上在applicationWillFinishLaunching执行的代码延后执行
优化八:优化rootViewController
rootViewController的加载,适当将某一级的childViewController或subviews延后加载
如果你的App可能会被后台拉起并冷启动,可考虑不加载rootViewController
优化九:小优化
- 不使用xib,直接视用代码加载首页视图
- NSUserDefaults实际上是在Library文件夹下会生产一个plist文件,如果文件太大的话一次能读取到内存中可能很耗时,如果耗时很大的话需要拆分(需考虑老版本覆盖安装兼容问题)
- 每次用NSLog方式打印会隐式的创建一个Calendar,因此需要删减启动时各业务方打的log,或者仅仅针对内测版输出log
- 梳理应用启动时发送的所有网络请求,是否可以统一在异步线程请求
})