原文: Regarding Swift build time optimizations
作者: Robert Gummesson
译者: 孙薇
上周我拜读了Nickoneill的佳作《加速Swift的构建》,之后便不由稍微换了个角度来看待Swift的代码。
目前有一个新问题出现:是否该将一行可以算是简洁的代码重构为9行代码,以便符合编译器的需求?对于代码来说,简洁与编译器友好孰轻孰重?其实,这取决于项目的规模以及开发者的意愿。
等等,有一个Xcode插件
在举例之前,我先强调一下手动过滤日志文件是非常耗时的。有人设计出了终端命令来简化这一步骤,不过我的方法更进一步,使用了Xcode插件。
虽然初衷只是为了找到并修复最耗时的部分,不过目前我的看法也已经发生了变化:将该过程当成迭代的过程。这样的做法不仅可以让代码构建效率更高,也能在最开始就防止项目中出现过于耗费时间的函数。
不少的惊喜
我时常来来回回地查看不同的Git branch,等待一个项目花费很长时间编译结束,一直没想出来为什么一个小项目(大约2万行Swift代码)要花那么久时间构建。
在了解到真正的原因后,我必须承认:一行代码也需要好几秒来编译这件事吓了我一跳。
我们来看几个例子!
空合运算符(Nil Coalescing Operator)
很显然,编译器肯定不喜欢第一种编译方法,在拆成两个视图之后,构建时间减少了99.4%。
// Build time: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22,height: bounds.height)
// Build time: 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]
// rather than
ArrayOfStuff.append(stuff)
return ArrayOfStuff
我经常会这样写代码,而这种方式对代码的构建时间有很大影响。下面是差距最大的一个案例,构建时间减少了97.9%。
// Build time: 1250.3ms
let systemOptions = [ 7,14,30,-1 ]
let systemNames = (0...2).map{ String(format: localizedFormat,systemOptions[$0]) } + [NSLocalizedString("everything",comment: "")]
// Some code in-between
labelNames = Array(systemNames[0..<count]) + [systemNames.last!]
// Build time: 25.5ms
let systemOptions = [ 7,-1 ]
var systemNames = systemOptions.dropLast().map{ String(format: localizedFormat,$0) }
systemNames.append(NSLocalizedString("everything",comment: ""))
// Some code in-between
labelNames = Array(systemNames[0..<count])
labelNames.append(systemNames.last!)
三元运算符(Ternary operator)
使用if else语句代替了三元运算符,而不进行其它任何改动,构建时间就能缩短92.9%。如果用for循环来代替map,构建时间又会缩短75%(不过for循环太伤眼了)。
// Build time: 239.0ms
let labelNames = type == 0 ? (1...5).map{type0ToString($0)} : (0...2).map{type1ToString($0)}
// Build time: 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%。
// Build time: 3431.7 ms
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180
// Build time: 3.0ms
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180
Round()
这个真的很奇怪,下面样例中的变量混合了本地与实例变量,问题很可能不在rounding本身,而是由于在方法中混用代码而导致的,去掉它之后就能大幅缩减所用的时间,准确来讲耗费时长减少了97.6%。
// Build time: 1433.7ms
let expansion = a — b — c + round(d * 0.66) + e
// Build time: 34.7ms
let expansion = a — b — c + d * 0.66 + e
注意:所有的对比测量都是在MacBook Air(13英寸,Mid 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()
}
}
第一时间掌握最新移动开发相关信息和技术,请关注mobilehub公众微信号(ID: mobilehub)。