背景

目前App项目不大,但是清空后重新编译时间需要200多秒,感觉不太合理,所以,就着手排查了一下。

通常的编译时间优化都是分为三个部分

  • Xcode编译设置的优化
  • 代码或函数编译时间的优化
  • 三方库编译时间的优化

这里就根据上面三个部分来一一排查。

实现

Xcode编译设置的优化

使用的是Xcode 13.4,网上搜到的,关于Xcode设置New Build System,及Build Settings中设置Debug Information Format的都不需要设置了,默认已经是合理的。至于Optimization Level的设置,设置后,虽然编译速度可以提升,但是对于Debug不友好,所以,这里也不做设置。故而针对这项优化什么都没有处理。

代码或函数编译时间的优化

这方面主要是针对Swift,首先把编译耗时的方法显示出来,在Build SettingsOther Swift Flags添加如下设置,意思是显示编译超过200ms的函数或者类型检查超过300ms的函数显示warning,这里200ms是自己设定,可针对项目的真实情况设置:

1
2
3
4

-Xfrontend -warn-long-function-bodies=200
-Xfrontend -warn-long-expression-type-checking=200

笔者这里修改的内容有下面这几种:

待优化代码示例1:

1
2
3
4
5

let count:Int = Int((self?.listParamItem.pageSize ?? 0) * ((self?.needRefreshPageNum ?? 0) - 1))
let endIndex = count + Int(self?.listParamItem.pageSize ?? 0) - 1
if (self?.dataList.count ?? 0) > endIndex {

上面的代码中,可空和 ??混用然后类型转换,再去进行比较,代码虽然没错,但是编译真的耗时,这些方法超出了500ms。针对这些修改为如下代码,编译时间变为100ms以内,减少了5倍:

1
2
3
4
5
6
7

if let self = self {
let count: Int = self.listParamItem.pageSize * (self.needRefreshPageNum - 1)
let endIndex: Int = count + self.listParamItem.pageSize - 1
if self.dataList.count > endIndex {
}

待优化代码示例2:

1
2
3
4
5
6
7

let dic = [
"aaa": xxx ?? yyy,
"bbb": ["ccc": "xxx", "eee": 5],
"ddd": 5
]

上面代码中,看起来没什么问题,但是编译时间超过了200ms,可能是类型推断导致。修改为如下代码后,编译耗时不再超过200ms:

1
2
3
4
5
6
7
8
9
10

var dic1: [String: Any] = [:]
dic1["ccc"] = "xxx"
dic1["eee"] = 5

var dic2: [String: Any] = [:]
dic2["aaa"] = xxx
dic2["bbb"] = dic1
dic2["ddd"] = 5

待优化代码示例3

1
2
3
4
5
6
7
8
9
10
11
12

if type == .aaa ||
type == .bbb ||
type == .ccc ||
type == .ddd ||
type == .eee ||
type == .xxx {
doSomething()
} else {
doAnotherThing()
}

上面的代码除了不优雅外,编译还耗时,修改为Switch case如下:

1
2
3
4
5
6
7
8
9
10
11
12
13

switch type {
case .aaa,
.bbb,
.ccc,
.ddd,
.eee,
.xxx:
doSomething()
default:
doAnotherThing()
}

待优化代码示例4

1
2
3
4
5
6
7
8
9

let fontAdd: CGFloat = 14.0

protocolBtn.snp.makeConstraints { make in
make.left.equalTo(agreeLabel.snp.right).offset(1)
make.centerY.equalTo(checkBtn.snp.centerY)
make.width.equalTo(kTransitionW(150 + fontAdd * 10))
}

上面的代码中小数和整数混合运算,让Swift来推断需要的是小数还是整数。修改为如下后,编译超时警告消失:

1
2
3
4
5
6
7
8
9
10

let fontAdd: CGFloat = 14.0
let width: CGFloat = 150.0 + fontAdd * 10.0

protocolBtn.snp.makeConstraints { make in
make.left.equalTo(agreeLabel.snp.right).offset(1.0)
make.centerY.equalTo(checkBtn.snp.centerY)
make.width.equalTo(kTransitionW(width))
}

最后还有一类是某个方法特别长的,也出现了编译超时警告,拆分为多个子方法解决。

三方编译库的优化

项目中集成的三方库都是CocoaPods的方式集成,所以每次clean build后,三方库都会重新编译。设置Xcode显示编译时间,打开terminal,复制下面,去运行,重启Xcode。

1
2
3

$ defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

然后clean,重新build,可以看到详细的编译时间如下:

编译时间

这里的ZLPhotoBrowser就是通过Pod方式安装的第三方库,可以看到这个库的源文件编译时间用了30秒。。。这个页面可以详细看到每个库的编译时间和项目的编译时间,针对编译时间长的三方库,改为Carthage方式导入,可参考Carthage的使用Carthage是将要使用的第三方库,下载并编译为xcframework,然后把生成的xcframework导入到工程中使用,故而每次clean build后,并不会重新编译,从而可以节省编译时间。

总结

编译时间优化的三个部分总结如下:

  • Xcode编译设置的优化——新版本Xcode无需设置
  • 代码或函数编译时间的优化——需要注意类型推断和复杂计算的优化以及运算符的使用,但是并不是所有的代码编译超时都需要进行优化,在编译优化和代码的简洁、优雅、Swift特性之间,要自己选择平衡点。
  • 三方库编译时间的优化——这个优化推荐使用,针对不会更改的三方库,改为Carthage方式导入,可以减少重新编译时间。

建议大家优化前,首先分析项目的编译时间卡在哪里,分析是三方库的编译时间太长还是项目源文件的编译时间太长,从而决定着重进行步骤二还是步骤三。

参考