App 启动速度优化与监控 - wanshanhu79/Study GitHub Wiki
内容主要来自 极客时间《iOS开发高手课-02 | App 启动速度怎么做优化与监控?》
一般而言,App的启动时间指的是从用户点击App开始,到用户看到第一个界面之间的时间。 主要分为三个阶段:
- 1.main()函数执行前;
- 2.main()函数执行后;
- 3.首页渲染完成后。
- 加载可执行文件(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中,会使应用提交评审时的代码段非常大,苹果
官方文档对代码段有限制,会被拒绝。
main()函数执行后的阶段,指的是从main()函数执行开始,到Appdelegate的didFinishLaunchingWithOptions
方法里首屏渲染相关方法执行完成。
首页的业务代码都是要在这个阶段,也就是屏渲染完成前执行的,主要包括以:
- 首屏初始化所需配置文件的读写操作
- 首屏列表大数据的读取
- 首屏渲染的大量计算等
从功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是App启动必要的初始化功能,而哪些是只需要在对应功能开始使用才需要初始化的。 放入合适的阶段初始化。
从渲染完成时开始,到didFinishLaunchingWithOptions方法作用域结束时结束。
主要完成的是非首屏其他服务模块的初始化、监听的注册、配置文件的读取。
从main()函数执行后的这个阶段下手。main()函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非首屏 业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后进行。
检查首屏渲染完成前主线程上有哪些耗时方法,将没必要的耗时方法滞后或异步执行。 通常情况下,主要表现为计算大量数据,加载、编辑、储存图片和文件等资源。
对App启动速度的监控,主要有两种手段
Xcode自带的工具套件Time Profiler,采用的就是这种方式。
hook方法的意思是,在原方法开始执行时换成其他你指定的方法,或者在原有方法执行前后执行你指定的方法,来掌握和改变 指定方法的目的。
对于C和block,可以使用libffi 的 ffi_call来达成hook,但缺点就是编写维护相关工具门槛高。
Facebook开源了一个库,可以在iOS上运行的Mach-O二进制文件中动态
地重新绑定符号,fishhook
大致思路是,通过重新绑定符号,可以实现对c方法的hook。dyld是通过更新Mach-O二进制
的__DATA segment特定的部分中的指针来绑定lazy和non-lazy符号,通过确认传递给rebind_symbol
里每个符号名称更新的位置,就可以找出对应替换来重新绑定这些符号。