App 启动速度怎么做优化与监控?

App 启动时都做了什么?
一般情况下,App 的启动分为冷启动和热启动。

  1. 冷启动是指, App 点击启动前,它的进程不在系统里,需要系统新创建一个进程分配给它启动的情况。这是一次完整的启动过程。(用户感知到的启动慢,就是主线程上存在较多的耗时方法,如:大文件的读写操作、在渲染周期中执行了大量的计算等等)
  2. 热启动是指 ,App 在冷启动后用户将 App 退后台,App 的进程还在系统里的情况下,用户重新启动进入 App 的过程,这个过程做的事情非常少。

以下是App冷启动的相关优化:

一、App的启动主要包括三个阶段:

点击App,main()函数执行前,main()函数执行后,首屏渲染完成后,启动完成

1. main()函数执行前;
  • 加载可执行文件(App的.o文件的集合)
  • 加载动态链接库,进行 rebase 指针调整和 bind 符号绑定;
  • Objc运行时的初始处理,包括Objc相关类的注册、category注册、selector唯一性检查等等
  • 初始化,包括执行+load()方法、attribute((constructor))修饰的函数调用、创建C++静态全局变量。
    在这个阶段的优化启动速度,可以:
  • 减少动态库加载。每个库本身有依赖关系,数量较多时,可以合并多个动态库,苹果建议最多使用6个非系统动态库
  • 减少加载启动后首屏不使用的类或者方法。
  • +load()方法里的内容放在首屏渲染完成后再执行,或使用+initialize()方法替换掉。【+load()里进行运行时方法替换操作会有4毫秒的耗时,+load()方法的越多执行将拖慢App启动速度】
  • 减少C++全局变量的数量。
2. main()函数执行后;

指的是从main()函数执行开始,到appDelegate的didFinishLaunchingWithOptions方法里首屏渲染相关方法执行完成。

首页的业务代码都在这个阶段执行的,包括了:

  • 首屏初始化所需配置文件的读写操作;
  • 首屏列表大数据的读取;
  • 首屏渲染的大量计算等。

很多时候,开发者会把各种初始化工作都放到这个阶段执行,导致渲染完成滞后。
更加优化的开发方式,应该是

  • 从功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 启动必要的初始化功能,而哪些是只需要在对应功能开始使用时才需要初始化的。梳理完之后,将这些初始化功能分别放到合适的阶段进行。
3. 首屏渲染完成后。

主要完成的是,非首屏其他业务服务模块的初始化、监听的注册、配置文件的读取等。从函数上来看,这个阶段指的是设置了self.window.rootViewController开始到didFinishLaunchWithOptions方法作用域 结束,用户可以看到首页的信息了,然后处理一下会卡主线程的方法,来避免影响用户对首页的操作。

二、具体优化方法

1. 减少+load()的使用

使用+initialize()的方法代替+load(),注意把逻辑移动到+initialize()时,需避免+initialize()的重复调用问题,可以使用dispatch_once()让逻辑只执行一次。或者使用- viewDidLoad()

2. 对多个动态库进行合并,非系统动态库不超6个
3. 优化类、方法、全局变量

减少加载启动后不使用的类或方法;控制C++全局变量的数量

4.功能级别的启动优化

main()开始执行后到首屏渲染完成前,只处理首屏相关的业务,其他的都放到首屏渲染完成后去做。

5.方法级别的启动优化

首先检查首屏渲染完成前主线程上的耗时操作,将没必要的操作滞后或异步。通常耗时操作有:加载、编辑、存储图片和文件等资源。

三、查看耗时

1.查看Main()调用前的总耗时

设置环境变量 在Product->Scheme->Edit Scheme->Run->Arguments->Environment Variables->DYLD_PRINT_STATISTICS设置为YES,就可以在控制台中查看main函数执行前总共花费的多长时间。控制台会输出:Total pre-main time: 388.69 milliseconds (100.0%)还有dylib、rebase/binding、Objc Setup、initializer、dylib的加载时间。

2. 查看加载了多少动态库

在Xcode中选择工程文件,然后选择Targets,里面有Build Phases,这里面有Link Binary With Libraries(20 items),这里包含了系统和自己的动态库。

3.查看Main函数启动后的耗时

可以使用一些工具来监控,也可以在didFinishLaunchingWithOptions开始前打印时间,在App显示完成第一个界面再打印一个时间,计算时间差,就是main函数调用后到界面显示出来的总耗时了,有一个好用的工具:BLStopwatch

想准确知道时间花在了哪里,可以使用以下方法:

4.监控App启动耗时,精准找出方法,进行优化

两种准确监控的方法
1.定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。Xcode自带的Time Profile就是这样。
2.对objc_msgSend方法进行hook来掌握所有方法的执行耗时。
实现耗时监控工具思路:
(1). 通过定时器,每隔0.01s,获取一次主线程的函数堆栈,将函数名称、函数地址、函数耗时模型化为TimeModel,保存在callStackDict中,其中key为函数地址,value为TimeModel
(2). 定时执行的回调中,每次都判断函数地址是否存在,如果已经存在此函数地址,就讲对应的TimeModel中的耗时增加0.01s;如果不存在此函数地址,就初始化一个TimeModel,并将时间设置为0.01s。
(3). 当主界面显示完成之后,输出此callStackDict,即可查看主线程中每个方法的耗时


参考:戴铭-极客时间02,贝聊科技-一次立杆见影的启动时间优化,冰风v落叶-iOS优化App冷启动速度

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 3,267评论 0 5
  • 为了让我有一个更快速、更精彩、更辉煌的成长,我将开始这段刻骨铭心的自我蜕变之旅!从今天开始,我将每天坚持阅...
    李薇帆阅读 1,892评论 0 3
  • 似乎最近一直都在路上,每次出来走的时候感受都会很不一样。 1、感恩一直遇到好心人,很幸运。在路上总是...
    时间里的花Lily阅读 1,338评论 0 1
  • 1、expected an indented block 冒号后面是要写上一定的内容的(新手容易遗忘这一点); 缩...
    庵下桃花仙阅读 508评论 0 1
  • 一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V...
    墨雅丫阅读 522评论 0 0