UI卡顿优化
卡段检测
监测runloop
启动优化
总体来说,启动分为热启动和冷启动。热启动是app进入后台之后被挂起,用户再次让app进入前台的启动(所以热启动的流程是?)目前来看appDelegate 调用的某些方法,冷启动是app完成被杀死,用户再次点击图标打开app的过程。
冷启动流程
冷启动主要分为pre-main阶段和main 函数阶段
pre-main 阶段
1.dylib 引导程序
2.objc 的runtime setup
3.initalizer time(load) 方法
在pre-阶段,主要的优化手段有二进制重排和统计+load 方法耗时。
二进制重排
在app启动的时候,操作系统并没有真实地为app分配足够的物理内存,而是分配的虚拟内存(虚拟内存技术),生成一张虚拟内存和真实内存的关联表。当app需要使用内存时候,存在两种情况
- 如果已经申请了则通过表的记录访问物理内存地址,
- 如果没有申请则申请一块物理内存空间并记录在表中(
Page Fault
)
Page Fault
当数据未在物理内存会进行下列操作
- 系统阻塞该进程
- 将磁盘中对应
Page
的数据加载到内存 - 把虚拟内存指向物理内存
上述行为就就是Page Fault
综上所述,为了加快启动时间,就需要尽量减少"Page Fault",所以尽可能地把启动代码放到同一个地方,减少缺页中断。而把“启动代码”放到同一个内存页上的方法,就是二进制重排。
二进制重排的核心思想是方法符号重排,使启动的相关方法排在最前面从而减少启动Page Falut
的数量。
总的流程如下: 1.修改Xcode 配置 添加 编译期插桩 && 获取方法符号。 2.提取order file文件 3.设置order file 文件 4.编译运行代码 参考链接:juejin.cn/post/684490… 总体来说,效果非常差,就我目前项目,重排前启动时间0.9s,重排后启动时间为1.1s,所以这个方案并没有像往上所说的能大大缩短启动时间。
Hook +load 方法统计耗时
Hook objc的+load 方法主要利用了objc 向 dyld 注册的init 回调。
当 dyld 将要执行载入 image 的 initializers 流程时 (依赖的所有 image 已走完 initializers 流程时),init 回调被触发,在这个回调中,objc 会按照父类 - 子类 - 分类顺序调用 +load 方法。因为 +load 方法执行地足够早,并且只执行一次,所以我们通常会在这个方法中进行 method swizzling 或者自注册操作。也正是因为 +load 方法调用时间点的特殊性,导致此方法的耗时监测较为困难,而如何使监测代码先于 +load 方法执行成为解决此问题的关键点。
初始化流程如下:
1.All initializers in any framework you link to.
2.All +load methods in your image.
3.All C++ static initializers and C/C++ attribute(constructor) functions in your image.
4.All initializers in frameworks that link to you.
为了方便描述,这里我统称 2、3 步骤为 initializers 流程。可以看到,只要我们把监测代码塞进依赖动态库的 initializers 流程里 (监测耗时库),就可以解决执行时间问题 。考虑到工程内可能添加了其他动态库,我们还需要让监测耗时库的初始化函数早于这些库执行。解决了监测代码的执行问题,接下来就可以实现这些代码了,本文采用在 attribute(constructor) 初始化函数中 hook 所有 +load 方法来计算原 +load 执行的时间。之后逐个+load 方法优化
把动态库修改成静态库
由于pre-main阶段有一个动态库链接的过程,过多的动态库会导致链接过程太长,所以把动态库改成静态库,也是一个启动优化的方法。
热启动
针对热启动,主要app 后台进入前台的时候,调用becomeActive 方法