欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

优化Swift的构建时间

程序员文章站 2024-03-23 13:16:28
...

原文: Regarding Swift build time optimizations
作者: Robert Gummesson
译者: 孙薇
审校: 唐小引(@唐门教主),欢迎技术投稿、约稿,给文章纠错,请发送邮件aaa@qq.com

上周我拜读了Nickoneill的佳作《加速Swift的构建》,之后便不由稍微换了个角度来看待Swift的代码。

目前有一个新问题出现:是否该将一行可以算是简洁的代码重构为9行代码,以便符合编译器的需求?对于代码来说,简洁与编译器友好孰轻孰重?其实,这取决于项目的规模以及开发者的意愿。

等等,有一个Xcode插件

在举例之前,我先强调一下手动过滤日志文件是非常耗时的。有人设计出了终端命令来简化这一步骤,不过我的方法更进一步,使用了Xcode插件

优化Swift的构建时间

虽然初衷只是为了找到并修复最耗时的部分,不过目前我的看法也已经发生了变化:将该过程当成迭代的过程。这样的做法不仅可以让代码构建效率更高,也能在最开始就防止项目中出现过于耗费时间的函数。

不少的惊喜

我时常来来回回地查看不同的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, 14, 30, -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)。

优化Swift的构建时间