原文连接:
Regarding Swift build time optimizations
上周,在我读完
@nickoneill 写的一篇优秀的博文《
为缓慢的Swift编译时间提速》后,我发现用一个不一样的角度去审视 Swift 代码并非很难的一件事。
能够被认为是简洁的一行代码如今引起了一个新的问题 -- 是否应该把这行代码重构成对应的9行代码以让编译器更容易工做(看看接下来要讲的关于空合运算符(nil coalescing operator)的示例)?到底哪一个才是更重要的,简洁的代码仍是对编译器友好的代码?这取决于项目的大小和开发者的想法。
慢着。。。这里有一个 Xcode 插件
在展现具体的例子以前,我先想到就是手动查看日志是一件很是耗时的事情。有人提出了用终端命令可让这件事情变得比较容易,可是我更进一步,把这个用
Xcode 插件 给实现出来了。

对我来讲,最初的目的就是找到并修复最耗时的地方,但我如今的想法是让它经历更多的迭代过程。这样的话我就不只可让代码编译更有效率,还能够防止第一次进入项目的耗时。
更加惊喜的是
我常常在多个 Git 分支间跳来跳去,等待一个缓慢的项目编译完成每每浪费了大量的时间。我想了好长一段时间为何个人一个宠物项目会编译地这么缓慢(大概2万行 Swift 代码)。
在我学习了到底是缘由致使的这件事以后,我不得不认可个人确很吃惊,一行代码就须要几秒钟来编译。
让咱们一块儿看看几个例子。
空合操做符
编译器是很不喜欢这里的第一种方式的。在展开下面两处简写的代码以后,编译时间减小了99.4%。
// 编译时间: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)
// 编译时间: 32.4ms
var padding: CGFloat = 22
if let rightView = rightView {
padding += rightView.bounds.width
}
if let leftView = leftView {
padding += leftView.bounds.width
}
return CGSizeMake(size.width + padding, bounds.height)
复制代码
ArrayOfStuff + [Stuff]
这个看起来像下面这样:
return ArrayOfStuff + [Stuff]
// 而不是
ArrayOfStuff.append(stuff)
return ArrayOfStuff复制代码
我常常这样作,每次都会对所需的编译时间产生影响。下面是最差的一个,这里的编译时间减小了97.9%。
// 编译时间: 1250.3ms
let systemOptions = [ 7, 14, 30, -1 ]
let systemNames = (0...2).map{ String(format: localizedFormat, systemOptions[$0]) } + [NSLocalizedString("everything", comment: "")]
// 一些中间的代码
labelNames = Array(systemNames[0..<count]) + [systemNames.last!]
// 编译时间: 25.5ms
let systemOptions = [ 7, 14, 30, -1 ]
var systemNames = systemOptions.dropLast().map{ String(format: localizedFormat, $0) }
systemNames.append(NSLocalizedString("everything", comment: ""))
// 一些中间的代码
labelNames = Array(systemNames[0..<count])
labelNames.append(systemNames.last!)
复制代码
三元运算符
仅仅只是把三元运算符替换成 if-else 语句,就让编译时间减小了92.9%。若是将 map 换成 for 循环,就又能减小75%(可是那样的话个人眼睛可就受不了了)。😉
// 编译时间: 239.0ms
let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)}
// 编译时间: 16.9ms
var labelNames: [String]
if type == 0 {
labelNames = (1...5).map{type0ToString($0)}
} else {
labelNames = (0...2).map{type1ToString($0)}
}复制代码
转换 CGFloat 到 CGFloat
没听懂我在说什么?其实下面例子中值已是 CGFloat 了,而且有些括号是多余的。在清理完这些冗余以后,编译时间减小了99.9%。
// 编译时间: 3431.7 ms
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180
// 编译时间: 3.0ms
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180
复制代码
Round()
下面是一个很奇怪的例子,下面的例子中变量是一个局部变量与实例变量的混合。这个问题彷佛不是出在四舍五入自己,而是在于结合代码的方法。去掉四舍五入的方法大概能减小 **97.6%** 的构建时间。
// 编译时间: 1433.7ms
let expansion = a — b — c + round(d * 0.66) + e
// 编译时间: 34.7ms
let expansion = a — b — c + d * 0.66 + e
复制代码
注意:以上全部测试都在MacBool Air(13英寸,2013年中)上进行。
尝试一下吧
无论你是否面临过编译时间太长的问题,编写对编译器友好的代码都是很是有用的。我确信你会在其中找到一些惊喜。做为参考,这里有完整的代码,个人工程中能够5秒内完成编译…
import UIKit
class CMExpandingTextField: UITextField {
func textFieldEditingChanged() {
invalidateIntrinsicContentSize()
}
override func intrinsicContentSize() -> CGSize {
if isFirstResponder(), let text = text {
let size = text.sizeWithAttributes(typingAttributes)
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)
}
return super.intrinsicContentSize()
}
}
复制代码