前文已经通过源码分析以及实战演练带领读者学习了APP启动的整个过程,既然知识已经学了,就要学以致用。
APP启动-优化总结:
main()函数之前的优化:
1、减少动态库加载。每个库本身都有依赖关系,苹果公司建议使用更少的动态库,并且建议 在使用动态库的数量较多时,尽量将多个动态库进行合并。数量上,苹果公司最多可以支 持 6 个非系统动态库合并为一个。
2、将动态库转换成静态库。
3、进行代码瘦身,合并或删除无效的ObjC类、Category、方法、C++ 静态全局变量等。
4、+load() 方法里的内容可以放到首屏渲染完成后再执行,或使用 +initialize() 方法替换 掉。因为,在一个 +load() 方法里,进行运行时方法替换操作会带来 4 毫秒的消耗。不 要小看这 4 毫秒,积少成多,执行 +load() 方法对启动速度的影响会越来越大。
5、控制 C++ 全局变量的数量。
6、编译期clang插桩优化。
这是main()函数之前的优化,那么main()函数之后的优化要怎么做呢?
其实这个部分才是重点中的重点,因为大部分APP只要对main()函数后的时间做好优化就能满足400ms的启动要求。而且这部分的优化更贴合大众,相对也更简单。
main() 函数执行后的阶段,指的是从 main() 函数执行开始,到 appDelegate 的
didFinishLaunchingWithOptions 方法里首屏渲染相关方法执行完成。 首页的业务代码都是要在这个阶段,也就是首屏渲染前执行的,主要包括了:
首屏初始化所需配置文件的读写操作; 首屏列表大数据的读取; 首屏渲染的大量计算等。
很多时候,开发者会把各种初始化工作都放到这个阶段执行,导致渲染完成滞后。更加优化 的开发方式,应该是从功能上梳理出哪些是首屏渲染必要的初始化功能,哪些是 App 启动 必要的初始化功能,而哪些是只需要在对应功能开始使用时才需要初始化的。梳理完之后, 将这些初始化功能分别放到合适的阶段进行。
1、功能级别的启动优化
优化的思路是: main() 函数开始执行后到首屏渲染完成前只处理首屏相关的业务,其他非 首屏业务的初始化、监听注册、配置文件读取等都放到首屏渲染完成后去做。
具体可以考虑从这些角度优化:
1、用纯代码的方式,而不是 xib/Storyboard,来加载首页视图
2、延迟暂时不需要的二方/三方库加载;
3、延迟执行部分业务逻辑和 UI 配置;
4、延迟加载/懒加载部分视图;
5、避免首屏加载时大量的本地/网络数据读取;
6、在 release 包中移除 NSLog 打印;
7、在视觉可接受的范围内,压缩页面中的图片大小;
……
如果首屏为 H5 页面,针对它的优化,参考VasSonic的原理,可以从这几个角度入手:
终端耗时
webView 预加载:在 App 启动时期预先加载了一次 webView,通过创建空的 webView,预先启动 Web 线程,完成一些全局性的初始化工作,对二次创建 webView 能有数百毫秒的提升。
页面耗时(静态页面)
静态直出:服务端拉取数据后通过 Node.js 进行渲染,生成包含首屏数据的 HTML 文件,发布到 CDN 上,webView 直接从 CDN 上获取;
离线预推:使用离线包。
页面耗时(经常需要动态更新的页面)
并行加载:WebView 的打开和资源的请求并行;
动态缓存:动态页面缓存在客户端,用户下次打开的时候先打开缓存页面,然后再刷新;
动静分离:将页面分为静态模板和动态数据,根据不同的启动场景进行不同的刷新方案;
预加载:提前拉取需要的增量更新数据。
2、方法级别的启动优化
第一种方法是,定时抓取主线程上的方法调用堆栈,计算一段时间里各个方法的耗时。Xcode 工具套件里自带的 Time Profiler ,采用的就是这种方式。
可以参考:iOS 如何抓取线程的“方法调用栈”
第二种方法是,对 objc_msgSend 方法进行 hook 来掌握所有方法的执行耗时。
可以参考:自己做一个工具监控App的启动耗时