文章中,我会针对编译的各个细节进行说明,一旦我们了解了整个编译过程,我们就可以尝试在各个编译过程进行优化,提高编译速度

先了解一下Xcode 编译过程结构

预编译(PreProcessor)
  • 替换所有的宏

  • 查找所有的依赖

  • 处理预编译的指令

编译器(Compiler)
  • 转换源码为机器码(汇编)

这是swift的编译过程,具体流程可以参考我的这边文章

https://krela2010.github.io/2020/10/12/LLVM%E5%AD%A6%E4%B9%A0/

汇编(Assembler)
  • 将可读代码转为机器码
  • 转为Mach-O文件,Mach-O是二进制文件,可以直接在iOS,Mac系统上运行
链接(Linker)
  • 把不同的Mach-O目标文件合并到一起,生成一个可执行文件

    Object Files + dylib, .a , .tbd => Single Executable file

加载(Loader)
  • 最后一步,把可执行程序加载到内存里,执行.Loader分配内存空间.加载dylibs,动态库
什么是Clean Build

无缓存,全部编译

什么是Incremental Build

针对修改的文件,进行编译

把编译加速分为2个阶段
  1. 分析和设置编译选项,这个是最直接,最简单的方法
  2. 理解swift依赖关系,这个会影响到增量编译Incremental Build

一阶段:修改Build Settings

编译模式(Compliation Mode)

这个配置控制驱动driver和前端任务frontend jobs,先了解一下什么是驱动driver和前端frontend

驱动(Driver):

​ 任务调度,负责决定哪些文件需要编译或重新编译并运行(即作业)来执行编译和链接步骤

前端任务(frontend jobs):

​ 被Driver调用,运行swift-frontend,执行编译,生成PCH文件,合并模块等.这一步骤相当耗时

Primary File Mode

​ Driver把编译工作拆分出多个任务,每个任务读取模块中的所有文件,专注于编译主要的目标文件

Primary File Mode优点是做增量编译Incremental Build时,只针对过时需要编译的文件做处理,并且也可以同时运行多个编译工作,借助多核

Whole file Mode

​ Driver只运行一个编译任务,无论多个文件.一个读取所有文件,编译所有文件

Whole file Mode优点可以结合整个模块做优化,并且避免了Primary File Mode里编译初期重复工作,缺点是总是全量重编译

结合两种特点,建议Debug使用Primary File Mode, Release使用Whole file Mode Xcode默认也是这样配置的

Optimization Mode

​ 优化模式下执行swift编译器,SIL和LLVM耗费了大量的时间和系统资源

​ 非优化模式下,SIL和LLVM也会同样工作(仍会有一些优化处理),但是资源消耗有所下降,

有3种优化类型,

-Onone 代表无优化处理

-Osize代表优化文件大小

-O代表优化编译速度

复杂表达式和类型推断

类型推断是swift很实用的功能,可以不用指明类型,但也会增加大量的编译时间做类型推断工作

如何知道一个表达式耗费了大量的时间?

在开发环境,我们一般认为100ms以下是解析一个表达式的正常时间,如果超过100ms,我们要考虑做对表达式做一些优化,如何自动检查表达式的解析时间?

Build Settings — > Swift Compiler — > Custom Flags — > Other Swift Flags

添加

-Xfrontend -warn-long-expression-type-checking=100

还可以加一些级别例如250ms,500ms档

删除dSYM文件

dSYM文件(debug symbols file),记录App异常信息,存储在dSYM Bundle里,这个文件每次都会在编译时生成

可以Debug关闭,release开启

image-20201022121522124

运行脚本

运行脚本会在你每次编译都要执行一遍,对此做优化

二阶段:swift依赖关系

编译器知道项目的文件依赖关系,可以决定哪些需要顺序编译,哪些可以并行编译

有3条编译规则

规则1

如果函数体发生变化,swift不会重新编译相关依赖

原因

swift是类型安全语言,不关心你的函数体内的变化

规则2

当在文件里添加新函数,新结构体或者新的扩展时,swift会编译文件所有相关依赖

原因

​ 相较于oc,swift是基于文件编译的语言,你可以在你的文件里写任何的类和结构体,所以基于此,swift需要针对新增部分进行重编译

AllExtensions.swift里添加了所有用到的extensions,如果继续增加,相应的所有的Extension的依赖也要重新编译

如果是按照右边图示,把Extensions放到不同文件,只会编译对应文件的依赖

结论

不要把所有工具扩展放到一个文件里,拆分成不同文件

规则3

如果使用framework组件化结构,framework的任意变化都会是app重新编译所有文件

原文:

https://swift.org/swift-compiler/#compiler-architecture

https://bytes.swiggy.com/advanced-techniques-to-speed-up-the-compile-time-in-xcode-27819cb3be59