iOS应用启动速度优化
背景
- 项目规模增大,第三方 SDK 增多,app 启动速度受到影响
- 创新的页面,丰富的功能,如何保障用户体验
iOS 启动过程比较复杂,系统做了很多的加载和初始化工作,本文只涉及可以优化的部分,其他无需关注。
App 运行分析
1、exec()
main()函数是整个程序的入口,在程序启动之前,系统会调用exec()函数。- 作用是 根据指定的文件名找到可执行文件,并用它来取代进程的内容。
2、Dyld
- 动态链接器,加载应用所依赖的所有动态链接库。
dylib, 既动态库。- 获取到需要加载的动态库列表,找到对应的dylib,打开
Mach-O文件,将其注册到内核。 - 大部分都是系统
dylib, 系统的dylib会被预先计算和缓存起来,所以加载速度快。 - 想要深究
dylib,可参考源码
3、reabse/binding
- 在加载所有的动态链接库之后,他们只是处在相互独立的状态,需要将他们绑定起来,这就是
rebase/binding - 在
iOS 4.3之前,会把dylib加载到指定地址,所有指针和数据对于代码来说都是固定的,dylib就无需做reabse/binding。 -
在
iOS 4.3后引入了ASLR,dylib会被加载到随机地址,这个随机的地址跟代码和数据指向的旧地址会有偏差,dylib需要修正这个偏差,会重复不断的对需要rebase的指针加上一个偏移量。- Rebasing: 在镜像内部调整指针的指向。
- Binding:将指针指向镜像外部的内容。
4、Objc setup
这一步的主要工作是:
- 注册
Objc类,Objc Runtime需要维护一张映射类名与类的全局表。当加载一个dylib时,其定义的所有的类都需要被注册到这个全局表中。 - 把
category中定义的方法插入方法列表。 - 保证每个
selector的唯一性。
5、initializer
Objc的load函数。C++的构造函数属性函数。
优化
pre-main 阶段优化
app 启动有两种类型:
- Cold launch: app is not in kernel buffer cache.
- Warm launch: app and data already in mermory.
Cold launcg 耗时是我们需要关注的,在 xcode 8 后,我们可以测量 main 之前的耗时,方法如下: 在 Xcode 中 Edit scheme -> Run -> Arguments 添加环境变量 DYLD_PRINT_STATISTICS,并设置值为 1。然后运行 App,观察控制台输出。(因为dyld会有缓存,所以每次测量时需要重启手机)
打印如下:
Total pre-main time: 220.03 milliseconds (100.0%)
dylib loading time: 153.21 milliseconds (69.6%)
rebase/binding time: 4.31 milliseconds (1.9%)
ObjC setup time: 9.17 milliseconds (4.1%)
initializer time: 53.08 milliseconds (24.1%)
slowest intializers :
libSystem.B.dylib : 3.75 milliseconds (1.7%)
libMainThreadChecker.dylib : 20.14 milliseconds (9.1%)
oa : 47.09 milliseconds (21.4%)
可以看到 pre-main 主要经过 dylib loading -> rebase/binding -> ObjC setup -> initializer。
这几个步骤上文已经介绍,我们来看这几个不走哪些使我们能优化的。
1. 加载 dylib
- 减少不需要的动态库
- 合并非系统库
2. rebase/binding
- 对于
Objc来说就是减少class、secector和category。 - 为了不破坏代码易读性,只能减少不必要的类、代码。
3、Objc setup
无
4、initializers
- 减少
+load方法调用
main -> 首页 优化
经历过 pre-main 阶段,dylib 会调用 main() 函数,main() 函数会调用 UIApplicationMain()。如果在首页展示前我们处理了不必要的业务肯定会影响首页的加载。
这里我们可以借助 instruments工具找出耗时的操作,规避代码上的阻塞写法。为了尽快展示首页,尽量将优先级低的任务后加载。
任务分级
将启动中的所有任务进行梳理和分级,根据任务的优先级来制定优化方案。
- 一级:在首页加载前需要初始化的任务,优先级大于首屏展示。如:清理缓存、objection 注册,crashsdk 注册等必要的 sdk 注册。
- 二级: 构建 tabbar,首屏数据。
- 三级:优先级低的任务根据需要可以建立子线程,在首屏出来后加载耳朵任务, 如:检查更新、微信微博注册等
我们的didfinishLaunching可以分为这三个状态,方便app在对应的时机执行对应的任务。
代码优化
在整理完app的启动任务后,如果我们代码本身就有问题,同样还是会影响用户体验,在此总结出代码中存在的问题。
- 文件操作,图片下载等耗时任务不应阻塞主线程;
- 临时变量过多,建议使用autoreleasepool 以便及时释放;
- NSDateFormatter 重用;
- Cell重用;
- 合理使用单例;
- 复杂的页面不要使用xib;
- 避免循环引用;
- 使用懒加载。
总结
关于app启动优化,可以从几方面入手:
- 减少不必要的framework;
- 清理项目中没有使用到的代码、类,避免冗余代码;
- 慎用+load,第三方库已经使用了够多的+load方法(objection,rn…),在写代码时尽量用+initialize代替+load;
- 梳理流程,优化代码,启动模块化。耗时操作不要在主线程;
- 本地缓存。首页的数据离线化,优先展示本地缓存数据,等待网络数据返回之后更新缓存并展示;
- 懒加载,使用时再去创建;
- 网络优化,减少接口请求,接口合并,使用ip直连,除去DNS解析耗时,使用protobuf协议,使用http2协议。
转自 [干货 途牛iOS客户端启动速度优化实践](http://www.10tiao.com/html/241/201704/2650686830/1.html)