抖音品质建设 - iOS启动优化《原理篇》( 五 )


+ (void)load {printf("1234");}编译完了之后 , 这个函数会在二进制中的 TEXT 两个段存在:__text存函数二进制 , cstring存储字符串 1234 。为了执行函数 , 首先要访问__text触发一次 Page In 读入物理内存 , 为了打印字符串 , 要访问__cstring , 还会触发一次 Page In 。

  • 为了执行这个简单的函数 , 系统要额外付出两次 Page In 的代价 , 所以 load 函数多了 , page in 会成为启动性能的瓶颈 。
 
抖音品质建设 - iOS启动优化《原理篇》

文章插图
 
static initializer 产生的条件静态初始化是从哪来的呢?以下几种代码会导致静态初始化
  • __attribute__((constructor))
  • static class object
  • static object in global namespace
注意 , 并不是所有的 static 变量都会产生静态初始化 , 编译器很智能 , 对于在编译期间就能确定的变量是会直接 inline 。
//会产生静态初始化class Demo{ static const std::string var_1; };const std::string var_2 = "1234"; static Logger logger;//不会产生静态初始化static const int var_3 = 4; static const char * var_4 = "1234";std::string 会合成 static initializer 是因为初始化的时候必须执行构造函数 , 这时候编译器就不知道怎么做了 , 只能延迟到运行时~
UIKit Init+load 和 static initializer 执行完毕之后 , dyld 会把启动流程交给 App , 开始执行 main 函数 。main 函数里要做的最重要的事情就是初始化 UIKit 。UIKit 主要会做两个大的初始化:
  • 初始化 UIApplication
  • 启动主线程的 Runloop
由于主线程的 dispatch_async 是基于 runloop 的 , 所以在+load 里如果调用了 dispatch_async 会在这个阶段执行 。
Runloop线程在执行完代码就会退出 , 很明显主线程是不能退出的 , 那么就需要一种机制:事件来的时候执行任务 , 否则让线程休眠 , Runloop 就是实现这个功能的 。
Runloop 本质上是一个 While 循环 , 在图中橙色部分的 mach_msg_trap 就是触发一个系统调用 , 让线程休眠 , 等待事件到来 , 唤醒 Runloop , 继续执行这个 while 循环 。
Runloop 主要处理几种任务:Source0 , Source1 , Timer , GCD MainQueue , Block 。在循环的合适时机 , 会以 Observer 的方式通知外部执行到了哪里 。
抖音品质建设 - iOS启动优化《原理篇》

文章插图
 
那么 , Runloop 与启动又有什么关系呢?
  • App 的 LifeCycle 方法是基于 Runloop 的 Source0 的
  • 首帧渲染是基于 Runloop Block 的
 
抖音品质建设 - iOS启动优化《原理篇》

文章插图
 
Runloop 在启动上主要有几点应用:
  • 精准统计启动时间
  • 找到一个时机 , 在启动结束去执行一些预热任务
  • 利用 Runloop 打散耗时的启动预热任务
Tips: 会有一些逻辑要在启动之后 delay 一小段时间再回到主线程上执行 , 对于性能较差的设备 , 主线程 Runloop 可能一直处于忙的状态 , 所以这个 delay 的任务并不一定能按时执行 。
AppLifeCycleUIKit 初始化之后 , 就进入了我们熟悉的 UIApplicationDelegate 回调了 , 在这些会调里去做一些业务上的初始化:
  • willFinishLaunch
  • didFinishLaunch
  • didFinishLaunchNotification
要特别提一下 didFinishLaunchNotification , 是因为大家在埋点的时候通常会忽略还有这个通知的存在 , 导致把这部分时间算到 UI 渲染里 。
First Frame Render一般会用 Root Controller 的 viewDidApper 作为渲染的终点 , 但其实这时候首帧已经渲染完成一小段时间了 , Apple 在 MetricsKit 里对启动终点定义是第一个CA::Transaction::commit() 。
什么是 CATransaction 呢?我们先来看一下渲染的大致流程
抖音品质建设 - iOS启动优化《原理篇》

文章插图
 
iOS 的渲染是在一个单独的进程 RenderServer 做的 , App 会把 Render Tree 编码打包给 RenderServer , RenderServer 再调用渲染框架(Metal/OpenGL ES)来生成 bitmap , 放到帧缓冲区里 , 硬件根据时钟信号读取帧缓冲区内容 , 完成屏幕刷新 。CATransaction 就是把一组 UI 上的修改 , 合并成一个事务 , 通过 commit 提交 。


推荐阅读