app启动时间优化

1.背景

之前通过用户反馈平台发现有部分用户反馈我们的App 启动时间较长,同时从崩溃数据监控发现部分用户在App启动后崩溃, 通过日志排查我们发现崩溃的原因是App启动超时被系统kill掉了,于是我们开始分析项目中导致启动时间变长的原因,并对启动时间进行优化。

2.现状分析

当用户按下home键的时候,iOS的App并不会马上被kill掉,还会继续存活若干时间。理想情况下,用户点击App的图标再次回来的时候,App几乎不需要做什么,就可以还原到退出前的状态,继续为用户服务。这种持续存活的情况下启动App,我们称为热启动,相对而言冷启动就是App被kill掉以后一切从头开始启动的过程。我们这里只讨论App冷启动的情况。对于冷启动来说,启动时间是指从用户点击 APP 那一刻开始到用户看到第一个界面这中间的时间。

3.APP启动时间优化原则

对于启动时间优化其实就是遵循一个原则:尽早让用户看到首页内容。根据这一原则将一些非必须的操作尽量往后移,通常是移到首页显示后执行,同时对于无法往后移的操作,尽可能不占用主线程,主线程尽量只做 UI 操作,将其他操作移到子线程。

4.APP启动过程

iOS应用的启动可分为pre-main阶段和main()阶段,其中系统做的事情依次是:

pre-main阶段

解析Info.plist 

1.加载相关信息,例如如闪屏

2.沙箱建立、权限检查 

Mach-O加载

1.如果是胖二进制文件,寻找合适当前CPU类别的部分

2.加载所有依赖的Mach-O文件(递归调用Mach-O加载的方法)

3.定位内部、外部指针引用,例如字符串、函数等

4.执行声明为__attribute__((constructor))的C函数

5.加载类扩展(Category)中的方法

6.C++静态对象加载、调用ObjC的 +load 函数

main()阶段

1.调用main()

2.调用UIApplicationMain()

3.调用applicationWillFinishLaunching

5.启动耗时的测量

在进行优化之前,我们首先应该能测量各阶段的耗时。

pre-main阶段测量

在不越狱的情况下,以往很难精确的测量在main()函数之前的启动耗时,因而我们也往往容易忽略掉这部分数据。小型App确实不需要太过关注这部分。但如果是大型App(自定义的动态库超过50个、或编译结果二进制文件超过30MB),而我们的App的自定义动态库较多,且二进制文件接近60M,这部分耗时将会变得突出,需要我们优化pre-main阶段的时间。所幸,苹果已经在Xcode中加入这部分的支持。具体设置方法如下:

在Xcode 中 Edit scheme -> Run ->Auguments 将环境变量DYLD_PRINT_STATISTICS 设为1

还需要勾选下面这个选项:

设置好后把程序跑起来,控制台会有如下输出,pre-main阶段各过程的耗时

从图上可以看出加载时间最长的阶段,咱们可以有针对性的检查并优化此过程。 

时间耗时解读:

main()函数之前总共使用了2000ms

其中,加载动态库用了563.59.ms,指针重定位使用了910.80ms,ObjC类初始化使用了321.32ms,各种初始化使用了235.84ms。

我们从上面的表中可以清晰的看出在哪个阶段耗时比较多,从而为下一步的优化提供指导。

main()阶段测量

对于main()阶段,主要是测量main()函数开始执行到didFinishLaunchingWithOptions执行结束的耗时,就需要自己插入代码到工程中了。先在main()函数里记录当前时间,再在AppDelegate.m文件中的didFinishLaunchingWithOptions函数的最后获取一下当前时间,这两个的时间的差值即是main()阶段运行耗时。

6.优化思路及明确优化方向

在通过以上方法测量出各阶段的占用时间,从数据上分析哪个阶段占用的时间多,从而指导我们明确优化的方向。

是什么影响了我们的APP的启动时间?切忌挖空心思的研究优化main()函数调用之前的占用时间,反而忽略了-applicationDidFinishLaunching:函数之后那一堆堆臃肿的网络请求以及业务流程。

所以我们先来看看-applicationDidFinishLaunching:函数之后,我们的APP都做了哪些事情:

首先会初始化window,加载tabbar,加载首页controller以及数据,可能我们还有一个loading广告页,还有各种各样的业务需求,网络请求。所以这些都是需要去排查的地方,可以尝试通过添加打印时间戳的方式,来测量每个阶段的耗时情况。我们根据排查结果来明确造成启动缓慢的原因。

7.pre-main阶段加载过程

要对pre-main阶段的耗时做优化,需要再学习下dyld加载的过程,dyld的加载主要分为4步:

加载dylibs

这一阶段dyld会分析应用依赖的dylib(大部分是iOS系统的),找到其mach-o文件,打开和读取这些文件并验证其有效性,接着会找到代码签名注册到内核,最后对dylib的每一个segment调用mmap()。

Rebase/Bind

这一阶段系统主要注册 Objc 类。所以,指针数量越少越好。

Objc setup

OC的runtime需要维护一张类名与类的方法列表的全局表。

dyld做了如下操作:

对所有声明过的OC类,将其注册到这个全局表中(class registration);将category的方法插入到类的方法列表中(category registration);检查每个selector的唯一性(selectoruniquing)在这一步倒没什么优化可做的,Rebase/Bind阶段优化好了,这一步的耗时也会减少。

Initializers

这一阶段,dyld 开始运行程序的初始化函数,调用每个 Objc 类和分类的 +load 方法,调用

C/C++ 中的构造器函数。initializer阶段执行完后,dyld 开始调用 main() 函数。

8.pre-main阶段耗时优化

通过以上的pre-main阶段过程的分析我们得到如下结论:

动态库加载越多,启动越慢;ObjC类越多,启动越慢;C的constructor函数越多,启动越慢;C++静态对象越多,启动越慢;ObjC的+load越多,启动越慢。

实验证明,在ObjC类的数目一样多的情况下,需要加载的动态库越多,App启动就越慢。同样的,在动态库一样多的情况下,ObjC的类越多,App的启动也越慢。需要加载的动态库从1个上升到10个的时候,用户几乎感知不到任何分别,但从10个上升到100个的时候就会变得十分明显。同理,100个类和1000个类,可能也很难察觉得出,但1000个类和10000个类的分别就开始明显起来。

因此我们建议在pre-main阶段的优化如下:

1、尽量不要写__attribute__((constructor))的C函数,也尽量不要用到C++的静态对象;至于ObjC的+load方法,似乎大家已经习惯不用它了。任何情况下,能用dispatch_once()来完成的,就尽量不要用到以上的方法。

2、由于苹果对app二进制代码大小的限制,我们将app中很多基础控件和基础功能的静态库转成了很多个动态库,这导致了加载动态库时间耗费较长,为此我们将已有的多个动态库合并为一个动态库,减少加载dylib的时间。

3、排查清理项目中未使用到的类库以及Framework。

4、清理项目中无用的类,删减没有被调用到或者已经废弃的方法。

5、减少ObjC类(class)、方法(selector)、分类(category)的数量。

6、删减一些无用的静态变量。

7、检查 +load 方法,不要在+load方法里做耗时操作,尽量把事情推迟到 +initiailize 方法里执行, 也就是到使用时才加载。

8、减少C++静态全局变量的个数。

9.main()阶段的耗时优化

此阶段的优化才是我们app优化的核心与重点,大部分的启动时间消耗出现在此阶段:

这一阶段的优化主要是减少didFinishLaunchingWithOptions方法里的工作,在didFinishLaunchingWithOptions方法里,我们会创建应用的window,指定其rootViewController,调用window的makeKeyAndVisible方法让其可见。

由于业务需要,我们会初始化各个三方库,推送、定位、im、埋点上报等基础服务的初始化,检查是否需要显示引导页、是否需要登录、是否有新版本等,由于历史原因,这里的代码容易变得比较庞大,启动耗时难以控制。

所以,满足业务需要的前提下,didFinishLaunchingWithOptions在主线程里做的事情越少越好,可以把一些事情放在子线程去处理。

优化方向及方案

1、梳理各个三方库,找到可以延迟加载的库,做延迟加载处理,比如放到首页控制器的viewDidAppear方法里或者用到此功能的时候再去加载。

2、梳理业务逻辑,把可以延迟执行的逻辑,做延迟执行处理。比如检查新版本、注册推送通知等逻辑。

3、复杂的计算(例如UI控件的位置信息及mode的解析)放到子线程中去处理。

4、避免在首页控制器的viewDidLoad和viewWillAppear做太多事情,部分可以延迟创建的视图应做延迟创建/懒加载处理。

5. 首页控制器用纯代码方式来构建,xib及storyboard创建的界面第一次加载的时候相对来说要比纯代码加载速度稍慢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值