App的启动过程及优化分享

App的启动过程及优化分享

App 启动时都干了些什么事儿?

一般情况下,App 的启动分为冷启动和热启动。

  • 1、冷启动是指, App 点击启动前,它的进程不在系统里,需要系统新创建一个进程分配给它启动的情况。这是一次完整的启动过程。
  • 2、热启动是指,App再冷启动后用户将App退到后台,在App的进程还在系统里的情况下,用户重新启动进入App的过程,这个过程做的事情比较少。

用户能感知到的启动慢,其实都发生在主线程上。而主线程慢的原因有很多,比如在主线程上执行了大文件读写操作、在渲染周期中执行了大量计算等。
一般而言,App 的启动时间,指的是从用户点击 App 开始,到用户看到第一个界面之间的时间。总结来说,App 的启动主要包括三个阶段:

  • 1、main() 函数执行前;
  • 2、main() 函数执行后;
  • 3、首屏渲染完成后。

main() 函数执行之前
在这里插入图片描述

或者:

@UIApplicationMain
UIApplicationMain的内容
 *  @param argc   系统参数 
 *  @param argv   系统参数 
 *  @param nil    应用程序名称 
 *  @param class 应用程序代理名称 
 */ 
 UIApplicationMain(int argc, charchar *argv[], NSString *principalClassName, NSString *delegateClassName);

UIApplicationMain函数有四个参数,
argc、argv:直接传递给UIApplicationMain进行相关处理,包含了系统带过来的启动时间;
principalClassName:指定应用程序类名,该类必须是UIApplication(或子类)。如果为nil,则用UIApplication类作为默认值;delegateClassName:指定应用程序的代理类,该类必须遵守UIApplicationDelegate协议

UIApplicationMain函数会根据principalClassName创建UIApplication对象,根据delegateClassName创建一个delegate对象,并将该delegate对象赋值给UIApplication对象中的delegate属性 。接着会建立应用程序的Main Runloop(事件循环),进行事件的处理(首先会在程序完毕后调用delegate对象的application:didFinishLaunchingWithOptions:方法)。app启动时会加载Info.plist文件,看是否指定了main.storyboard,如果设置了就去加载main.storyboard,那么加载main.storyboard时,系统会进行如下操作:
创建窗口 -> 加载main.storyboard并且加载main.storyboard中指定的控制器 -> 创建控制器成为窗口的根控制器,让窗口显示出来。

这个阶段的耗时可以通过配置环境变量查看。DYLD_PRINT_STATISTICS
在这里插入图片描述
——————
从上图我们可以知道,这个阶段的系统工作大致为:
1、dylib loading | 加载动态链接库。dyld从可执行文件的依赖开始, 递归加载所有的依赖动态链接库。动态链接库包括:iOS 中用到的所有系统 framework、加载OC runtime方法的libobjc、系统级别的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。优化手段是减少动态库的加载,将多个动态库进行合并使用,以及使用静态资源,比如把代码加入主程序
2、 rebease/bind | 进行rebase指针调整和bind符号绑定。由于ASLR(address space layout randomization,一种针对缓冲区溢出的安全保护技术)的存在,可执行文件和动态链接库在虚拟内存中的加载地址每次启动都不固定,所以需要这2步来修复资源指针,指向正确的地址。rebase修复的是指向内部的资源指针,bind指向的是外部的资源指针。
3、 ObjC setup | 运行时初始处理,包括注册Objc类、把category的定义插入方法列表、保证每一个selector唯一等。这个部分的优化手段是定期清理项目中不再使用的类或者方法,因为在功能迭代时期可能会淘汰掉很多不再需要的功能,应该将这些文件及时清理。
4、 initializer | 初始化,包括了执行+load()方法、创建静态全局变量等。在一个+load()方法里,进行运行时方法替换会带来4毫秒的消耗,这部分消耗积少成多,可以选择使用+initialize()方法替换,或者将这部分内容放到首屏渲染完成后执行,进行分流。

由于这部分主要是系统级别的操作,所以我们能优化的范围很小,主要是要了解这个阶段系统的主要工作,尽量避免使用动态库,检查+load()方法等。

注:苹果建议的是在400ms内完成main()函数之前的加载,整体耗时不能超过20s,否则会杀掉进程,App启动失败。

main() 函数执行之后 和 首屏渲染完成后
首先我们先通过代码明确一下main() 函数执行之后和首屏渲染完成后具体是什么意思?
在这里插入图片描述
在这里插入图片描述
运行程序,控制台结果如下:
在这里插入图片描述

可以看出,
1、 main() 函数执行之后这个阶段,指的是从 AppDelegate 的 didFinishLaunchingWithOptions 方法开始,直到首页控制器的ViewDidLoad方法执行完毕。
2、 首页渲染完毕后这个阶段,指的是从首页控制器 ViewDidLoad 方法结束,直到 AppDelegate 的 didFinishLaunchingWithOptions 方法结束。

那么这两个阶段分别又执行什么任务呢?耗时又如何监控?

在 main() 函数执行之后 到 首屏渲染完成前,主要任务包括:
1、首屏初始化所需配置文件的读写
2、首屏所需数据的读取
3、首屏渲染的计算等
在 首屏渲染完成后,主要完成:
1、 非首屏业务模块的初始化
2、 系统监听注册
3、 配置文件的读取等
此时用户已经可以看到APP的首页了,以上这些不紧急的任务最好在这个阶段处理。

如何监控耗时?

  • 1、Xcode自带工具Time Profiler,定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。
  • 2、对objc_msgSend方法进行 hook 来掌握所有方法的执行耗时,hook方法的意思是,在原方法开始执行时换成执行其他你指定的方法,或者再原有方法执行前后执行你指定的方法,来达到掌握和改变指定方法的目的。
    hook objc_msgSend 方法,是基于Facebook开源的 fishhook 库。
    在这里插入图片描述
    理解了APP启动的流程,我们大概能感受到,启动速度的优化主要集中在didFinishLaunchingWithOptions 中,这里主要进行的工作就是:
  • 1、 初始化各种SDK
  • 2、 初始化各种工具类
  • 3、 配置各种监听等
    我们需要做的就是根据项目需求,有的放矢的安排各种工作的运行顺序并解偶 AppDelegate,巧妙的使用预加载方法,优化首页的布局等。

最后

优化工作应该是贯穿始终的
优化工作更需要前后端的密切配合。

参考资料

1、ASLR机制:https://www.jianshu.com/p/728f2ef139ae
2、https://www.jianshu.com/p/c603a1e69126

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章