App 启动速度优化与监控 - wanshanhu79/Study GitHub Wiki

内容主要来自 极客时间《iOS开发高手课-02 | App 启动速度怎么做优化与监控?》

App启动的三个阶段

一般而言,App的启动时间指的是从用户点击App开始,到用户看到第一个界面之间的时间。 主要分为三个阶段:

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

1.main()函数执行前

执行的操作

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

优化

  • 减少动态库加载,尽可能将多个动态库合并
  • 减少加载启动后不会去使用的类或者方法
  • +load()方法里的内容可以放到首屏渲染完成后再执行,或使用 +initialize()方法替换掉, 。在+load()方法里,进行运行时方法替换操作会带来4毫秒的消耗。积少成多
  • 控制C++全局变量的数量

iOS8开始,由于Extension的出现,苹果开始允许自建动态库并在iOS App中使用, 这样宿主App和插件之间共享动态库,iOS上的动态库是私有的,不允许进程间共享,因为不能将动态库放在除自身沙盒以外的 其它任何地方。
动态库在应用打包编译的时候,仅把链接信息编译到应用二进制可执行文件中,自身以类似资源的形式 以一个单独的framework文件存放在安装包中,将framework的加载推迟到运行时。
如果把动态库全改为静态库,也可以减少启动时间,静态库会打包到App的.o文件集合中, 时间会小于加载动态库的时长。但是全打包到.o中,会使应用提交评审时的代码段非常大,苹果 官方文档对代码段有限制,会被拒绝。

不给“爸爸”添麻烦 - iTOP iOS 动态库改造

2.main()函数执行后

main()函数执行后的阶段,指的是从main()函数执行开始,到Appdelegate的didFinishLaunchingWithOptions 方法里首屏渲染相关方法执行完成。
首页的业务代码都是要在这个阶段,也就是屏渲染完成前执行的,主要包括以:

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

从功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是App启动必要的初始化功能,而哪些是只需要在对应功能开始使用才需要初始化的。 放入合适的阶段初始化。

3.首屏渲染完成后

从渲染完成时开始,到didFinishLaunchingWithOptions方法作用域结束时结束。

主要完成的是非首屏其他服务模块的初始化、监听的注册、配置文件的读取。

优化

功能级别

从main()函数执行后的这个阶段下手。main()函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非首屏 业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后进行。

方法级别的启动优化

检查首屏渲染完成前主线程上有哪些耗时方法,将没必要的耗时方法滞后或异步执行。 通常情况下,主要表现为计算大量数据,加载、编辑、储存图片和文件等资源。

对App启动速度的监控,主要有两种手段

定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时

Xcode自带的工具套件Time Profiler,采用的就是这种方式。

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

hook方法的意思是,在原方法开始执行时换成其他你指定的方法,或者在原有方法执行前后执行你指定的方法,来掌握和改变 指定方法的目的。

对于C和block,可以使用libffi 的 ffi_call来达成hook,但缺点就是编写维护相关工具门槛高。

Facebook开源了一个库,可以在iOS上运行的Mach-O二进制文件中动态 地重新绑定符号,fishhook
大致思路是,通过重新绑定符号,可以实现对c方法的hook。dyld是通过更新Mach-O二进制 的__DATA segment特定的部分中的指针来绑定lazy和non-lazy符号,通过确认传递给rebind_symbol 里每个符号名称更新的位置,就可以找出对应替换来重新绑定这些符号。

⚠️ **GitHub.com Fallback** ⚠️