main() 函数执行前
1、加载可执行文件(App 的.o 文件的集合);
2、加载动态链接库,进行 rebase 指针调整和 bind 符号绑定;提高启动速度减少动态库加载非常关键
3、Objc 运行时的初始处理,包括 Objc 相关类的注册、category 注册、selector 唯一性检查等;
4、初始化,包括了执行 +load() 方法、attribute((constructor)) 修饰的函数的调用、创建 C++ 静态全局变量。+load() 方法里的内容可以放到首屏渲染完成后再执行,或使用 +initialize() 方法替换掉。因为,在一个 +load() 方法里,进行运行时方法替换操作会带来 4 毫秒的消耗。不要小看这 4 毫秒,积少成多,执行 +load() 方法对启动速度的影响会越来越大。
main() 函数执行后
从功能上梳理出哪些是首屏渲染必要的初始化功能
哪些是 App 启动必要的初始化功能
哪些是只需要在对应功能开始使用时才需要初始化的
梳理完之后,将这些初始化功能分别放到合适的阶段进行。
优化的思路是:
1、main() 函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非首屏业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后去做。
2、进一步要做的是检查首屏渲染完成前主线程上有哪些耗时方法,将没必要的耗时方法滞后或者异步执行。
可以注意的点有:
1、除了用户看到的第一屏内容所依赖的初始化方法(UI和基础服务),尽量以异步,甚至是后台线程的方式来做初始化。
2、其他服务的初始化通常都会等到真正需要调用的时候才被执行。
3、对于第三方库,都是在AppDelegate.didFinishLaunchWithOptions方法中执行的,而这个方法是在主线程上,会阻塞用户操作,虽然每个sdk都建议在didFinishLaunchWithOptions中做初始化,但其实大多都没有这么紧急,稍微延后一些也是完全可以接受的,这样就大大减轻了启动代码的工作量。
有哪些手段能够对启动方法耗时进行全面监控呢?
第一种方法是,定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。
第二种方法是,对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时。objc_msgSend 方法干的活儿,就是在运行时根据对象和方法的 selector 去找到对应的函数指针,然后执行。
打印方法调用栈
第一步,设计两个结构体:CallRecord 记录调用方法详细信息,包括 obj 和 SEL 等;ThreadCallStack 里面,需要用 index 记录当前调用方法树的深度。有了 SEL 再通过 NSStringFromSelector 就能够取得方法名,有了 obj 通过 object_getClass 能够得到 Class ,再用 NSStringFromClass 就能够获得类名。
第二步,pthread_setspecific() 可以将私有数据设置在指定线程上,pthread_getspecific() 用来读取这个私有数据。利用这个特性,我们就可以将 ThreadCallStack 的数据和该线程绑定在一起,随时进行数据存取。
第三步,因为要记录深度,而一个方法的调用里会有更多的方法调用,所以我们可以在方法的调用里增加两个方法 pushCallRecord 和 popCallRecord,分别记录方法调用的开始时间和结束时间,这样才能够在开始时对深度加一、在结束时减一。
网友评论