如何有效提高swift的编译速度
swift 作为一门新语言,受到了广大开发者的喜爱,苹果也极力在推swift,甚至最终会替换掉OC,笔者现在公司的项目也采用了swift编写(三方库除外),但发现一个很严重的问题就是编译速度实在是难以让人接受,因此笔者在如何提高swift的编译速度方面做了相关调研,并将所学到的应用到项目中,项目的编译速度得到了很大的提升。文章将从两方面来介绍如何提高swift项目的编译速度,一是从代码优化上,一是从编译器设置上。
前言#
在改善项目的编译速度前,有必要知道到底是哪些函数编译耗时,哪些文件编译耗时,Robert 一个swift爱好者为我们提供了一个统计函数编译时间的工具https://github.com/RobertGummesson/BuildTimeAnalyzer-for-Xcode,利用该工具能很方便的查出编译耗时的地方。
一代码层面优化#
1.尽量避免类型推断,能确定类型的一定要给出具体类型
func test1() {
let number = 32
let string = ""
let label = UILabel()
let dict = ["string1":"string","number":10,"label":label] as [String : Any]
var strings: [String] = []
}
func test2() {
let number:Int = 32
let string:String = ""
let label:UILabel = UILabel()
let dict:[String:Any] = ["string1":"string","number":10,"label":label] as [String : Any]
var strings: [String] = [String]()
}
屏幕快照 2017-05-01 22.36.45.png
test1采用了类型推断耗时37.3ms,test2采用了精确的类型定义耗时10.5ms,减少了近三倍多的编译时间。
2.nil类型问题
由于swift存在可选值,因此某些对象的值可能为空,这在代码处理时可能会导致编译很慢
func test3() ->Int {
var number1:Int?
var number2:Int?
var number3:Int?
return 10 + (number1 ?? 0) + (number2 ?? 0) + (number3 ?? 0)
}
func test4() ->Int {
var total = 10
var number1:Int?
var number2:Int?
var number3:Int?
if let number1 = number1 {
total = total + number1
}
if let number2 = number2 {
total = total + number2
}
if let number3 = number3 {
total = total + number3
}
return total
}
屏幕快照 2017-05-01 22.44.38.png
test3中number1,2,3可能存在nil,因此在返回时如果为nil,则给了默认值0,结果编译时间为7841.3ms,将近8s,太不可思议。而test4中对于可能为nil的情况下进行了可选值绑定来判断是否为nil,编译时间为1.7ms,编译时间跟test3不在一个量级上面,因此对于可能为nil的情况下,建议采用可选值绑定的方式来判断,避免采用三的处理方式。
3.+ +=运算
直接看代码
func test5() {
var arrays = [Int]()
let arr1 = [1,2,3]
let arr2 = [3,4,5]
arrays += arr1 + arr2 + [10]
}
func test6() {
var arrays:[Int] = [Int]()
let arr1 = [1,2,3]
let arr2 = [3,4,5]
arrays.append(contentsOf: arr1)
arrays.append(contentsOf: arr2)
arrays.append(contentsOf: [10])
}
屏幕快照 2017-05-01 22.54.22.png
test5采用+ 将数组进行合并耗时140.9ms,而test5采用系统提供的api进行合并耗时2.3ms,因此对于数组合并的情况建议采用test6的形式。
4.复杂表达式计算
直接看代码
func test7(string1:String,string2:String) {
let string = string1 + "你好" + string2 + "\(10)"
}
func test8(string1:String,string2:String) {
var string = string1
string = string + "你好"
string = string + string2
string = string + "\(10)"
}
屏幕快照 2017-05-01 22.59.05.png
test7表达式虽清晰,但复杂,编译耗时23.4ms,test8将test7的表达式拆成几部分,编译时间1.3ms,表达式越简单,编译时间越短,因此是编写简洁明了的表达式,还是编写对编译器友好的表达式,我们是需要权衡的。
5.函数放在extension中,比不放在extension中编译更耗时,使用闭包也比较耗时。
二 编译器层面优化编译时间#
- WHO
简单地说,Whole-Module Optimization(全模块优化,以下简称 WMO),即在编译项目时,将同属于一个 Module(可以理解为一个 Target、一个 Package)的所有源代码都串起来,进行整体的一个分析与优化,区别于 Single-File Optimization(单文件优化,以下简称 SFO),WMO 可以更好的统筹全局,去 inline 函数调用、排除死函数(即写了却从不调用的函数)等等,使编译速度加快。但问题来了,WMO 只是在 Release 模式下成为了默认且推荐的选项,在 Debug 模式下默认依然是 None。
2.利用Uber团队在利用swift3重写客户端中发现的黑科技
Uber 的开发团队偶然发现如果把所有 Model 文件全部合并到一个文件去编译, 那编译时间会从 1min 35s 减少到 17s, 那么我们如果把所有代码文件都合并到一起, 那就可以极大地优化编译速度了。
WHO(Whole-Module-Optimization) 也会把文件合并起来再进行编译, 实际使用时我们发现编译虽然快了, 但对于编译时间的减少还是远没有直接把文件合并到一起那么有效. 主要原因是因为 WHO 除了合并文件之外, 还会在预编译阶段做这些事情: 检测没有被调用的方法和类型, 在预编译期去掉它们,给没有被继承的类, 没有被继承的方法加上 final 标签, 给编译器提供更多信息, 以便这些方法被优化为静态调用或者是内联进去,这些优化会对于程序的效率有很大的提升, 但编译时间会有所增加。
Uber 的团队发现通过增加一个编译宏就可以做到只合并文件, 而不做优化. 进入工程文件设置 -> Build Setting -> Add User-Defined Settings, key 为 SWIFT_WHOLE_MODULE_OPTIMIZATION
, value 设为 YES
, 然后把优化级别设为 None
就可以了.