iOS进阶 - App启动优化

iOS进阶 - App启动优化

App启动三个阶段

  • main() 函数执行前
  • main() 函数执行后
  • 首屏渲染完成后

main() 函数执行前

  • 加载可执行文件(App的 .o 文件的集合)
  • 加载动态链接库,进行 rebase 指针调整和 bind 符号绑定
  • Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等
  • 初始化,包括了执行 +load() 方法,attribute(constructor) 修饰的函数的调用、创建 C++ 静态全局变量

相应的在这个阶段可以做的优化包括:

  • 减少动态库加载, Apple 建议使用更少的动态库,并且建议在使用动态库的数量较多时将多个动态库进行合并(最多支持6个)
  • 减少加载启动后不会使用的类或者方法
  • +load() 方法里的内容可以放在首屏渲染完成后再执行,或使用 +intialize() 方法替换掉。
  • 控制 C++ 全局变量的数量
静态库 (.a)

静态库可以看做是一个具有特定功能的代码块,如果app中引用了静态库,则在编译时会将静态库直接复制到app的可执行文件(也就是mach-o)中,使用静态库会导致mach-o文件过大,而mach-o文件直接影响app的启动时间和执行时占用的内存大小。

动态库 (.dylib .framework)

为了减少mach-o文件的大小,需要用到动态库。当app中引用了动态库时,动态库并不会被复制到app的mach-o文件中,只有当动态库真正被用到时,才会去加载(加载到内存中)和链接(动态库可能引用了其他库)动态库,可能是在app启动时或者是运行时。

所有系统库都属于动态库。

main() 函数执行后

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

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

更加优化的开发方式,应该是从功能上梳理出哪些是首屏渲染必要的初始化功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是App启动必要的初始化功能,而哪些是只需要在对应功能开始使用时才需要的初始化。梳理完之后将其分别放在合适的地方进行。

首屏渲染完成后

主要是非首屏其他业务服务模块的初始化、监听的注册、配置文件的读取等。从函数上来看,这个阶段就是截止到 didFinishLaunchingWithOptions 方法作用域内指定首屏渲染之后的所有方法执行完成。简单说就是从渲染完成时开始,到 didFinishLaunchingWithOptions 方法作用域结束时结束。

功能级别的启动优化

优化思路即:main() 函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非首屏业务的初始化、监听、配置文件读取等都放到首屏渲染完成后去做。

方法级别的启动优化

  • 检查首屏渲染完成前主线程上有哪些耗时方法,将没必要的耗时方法滞后或者异步执行。通常,耗时较长的方法主要发生在计算大量数据的情况下,具体的表现就是加载、编辑、存储图片和文件等资源。
  • 减少 +load() 方法自定义次数,使用 +intialize() 方法替换。
  • 若使用 RAC 等响应式框架,注意在首屏渲染之前不要过多的创建信号。

检查App启动速度的两种方法

1. 定时抓取主线程的方法调用堆栈,计算一段时间里各个方法的耗时,Xcode 工具套件里自带的 Time Profiler 采用的就是这种方式

2. 对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时

我们知道 OC 的方法最终是通过调用 objc_msgSend 方法实现消息发送,是 OC 方法执行的必经之路。
objc_msgSend 方法 的执行逻辑是: 先获取对象对应类的信息,再获取方法的缓存,根据方法的 selector查找函数指针,经过异常错误处理后,最终跳到对应函数的实现

耗时检查的三方库 GCDFetchFeed 其中的 SMLagMonitor 库,感兴趣可以研读其源码,修炼底层功底。

总结

启动速度优化和监控的重要性不言而喻,加快 App 的启动速度对用户体验提升是最大的。
主要需要开发者对启动阶段功能进行分类整理,合理地将和首屏渲染无关的功能滞后。

极客时间-iOS开发高手课 学习笔记

你可能感兴趣的:(❶,iOS开发,❺,性能优化)