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

  • 资源文件如 storyboard , asset 也会编译 , 编译后加载速度会变快
  • Mach-O 和资源文件一起 , 打包出最后的.app
  • 对.app 签名 , 防篡改

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

    文章插图
     
    编译编译器可以分为两大部分:前端和后端 , 二者以 IR(中间代码)作为媒介 。这样前后端分离 , 使得前后端可以独立的变化 , 互不影响 。C 语言家族的前端是 clang , swift 的前端是 swiftc , 二者的后端都是 llvm 。
    • 前端负责预处理 , 词法语法分析 , 生成 IR
    • 后端基于 IR 做优化 , 生成机器码
     
    抖音品质建设 - iOS启动优化《原理篇》

    文章插图
     
    那么如何利用编译优化启动速度呢?
    代码数量会影响启动速度 , 为了提升启动速度 , 我们可以把一些无用代码下掉 。那怎么统计哪些代码没有用到呢?可以利用 LLVM 插桩来实现 。
    LLVM 的代码优化流程是一个一个 Pass , 由于 LLVM 是开源的 , 我们可以添加一个自定义的 Pass , 在函数的头部插入一些代码 , 这些代码会记录这个函数被调用了 , 然后把统计到的数据上传分析 , 就可以知道哪些代码是用不到的了。
    Facebook 给 LLVM 提的order_file的 feature 就是实现了类似的插桩 。
    链接经过编译后 , 我们有很多个目标文件 , 接着这些目标文件会和静态库 , 动态库一起 , 链接出一个 Mach-O 。链接的过程并不产生新的代码 , 只会做一些移动和补丁 。
    抖音品质建设 - iOS启动优化《原理篇》

    文章插图
     
    • tbd 的全称是 text-based stub library , 是因为链接的过程中只需要符号就可以了 , 所以 Xcode 6 开始 , 像 UIKit 等系统库就不提供完整的 Mach-O , 而是提供一个只包含符号等信息的 tbd 文件 。
    举一个基于链接优化启动速度的例子:
    最开始讲解 Page In 的时候 , 我们提到 TEXT 段的页解密很耗时 , 有没有办法优化呢?
    可以通过 ld 的-rename_section , 把 TEXT 段中的内容 , 比如字符串移动到其他的段(启动路径上难免会读很多字符串) , 从而规避这个解密的耗时 。
    抖音品质建设 - iOS启动优化《原理篇》

    文章插图
     
    抖音的重命名方案:
    "-Wl,-rename_section,__TEXT,__cstring,__RODATA,__cstring","-Wl,-rename_section,__TEXT,__const,__RODATA,__const", "-Wl,-rename_section,__TEXT,__gcc_except_tab,__RODATA,__gcc_except_tab", "-Wl,-rename_section,__TEXT,__objc_methname,__RODATA,__objc_methname", "-Wl,-rename_section,__TEXT,__objc_classname,__RODATA,__objc_classname","-Wl,-rename_section,__TEXT,__objc_methtype,__RODATA,__objc_methtype"裁剪编译完 Mach-O 之后会进行裁剪(strip) , 是因为里面有些信息 , 如调试符号 , 是不需要带到线上去的 。裁剪有多种级别 , 一般的配置如下:
    • All Symbols , 主二进制
    • Non-Global Symbols , 动态库
    • Debugging Symbols , 二方静态库
    为什么二方库在出静态库的时候要选择 Debugging Symbols 呢?是因为像 order_file 等链接期间的优化是基于符号的 , 如果把符号裁剪掉 , 那么这些优化也就不会生效了 。
    签名 & 上传裁剪完二进制后 , 会和编译好的资源文件一起打包成.app 文件 , 接着对这个文件进行签名 。签名的作用是保证文件内容不多不少 , 没有被篡改过 。接着会把包上传到 iTunes Connect , 上传后会对__TEXT段加密 , 加密会减弱 IPA 的压缩效果 , 增加包大小 , 也会降低启动速度 (iOS 13 优化了加密过程 , 不会对包大小和启动耗时有影响) 。
    dyld3 启动流程Apple 在 iOS 13 上对第三方 App 启用了 dyld3 , 官方数据显示 , 过去四年新发布的设备中有 93%的设备是 iOS 13 , 所以我们重点看下 dyld3 的启动流程 。
    Before dyld用户点击图标之后 , 会发送一个系统调用 execve 到内核 , 内核创建进程 。接着会把主二进制 mmap 进来 , 读取 load command 中的 LC_LOAD_DYLINKER , 找到 dyld 的的路径 。然后 mmap dyld 到虚拟内存 , 找到 dyld 的入口函数_dyld_start , 把 PC 寄存器设置成_dyld_start , 接下来启动流程交给了 dyld 。


    推荐阅读