今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

iOS 編譯時間優化

背景#

目前的 App 项目规模不大,但是重新编译所需的时间超过了 200 秒,感觉不太合理,因此开始进行排查。

通常的编译时间优化可以分为三个部分:

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

下面将根据上述三个部分逐一进行排查。

实现#

Xcode 编译设置的优化#

使用的是 Xcode 13.4,根据网上的搜索结果,关于 Xcode 设置New Build System以及Build Settings中设置Debug Information Format的内容都不需要进行修改,因为默认设置已经是合理的。至于Optimization Level的设置,虽然可以提升编译速度,但对于调试不太友好,因此这里不做任何修改。因此,对于这一部分的优化,不需要进行任何处理。

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

这方面主要针对 Swift 语言。首先,将编译耗时的方法显示出来,在Build Settings中的Other Swift Flags中添加以下设置,意思是当编译时间超过 200ms 的函数或类型检查时间超过 300ms 的函数时,显示警告。这里的 200ms 是根据项目实际情况进行设定的:


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

以下是我修改的几个示例:

待优化代码示例 1:


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 倍:


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:


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

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


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

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

待优化代码示例 3:


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

上述代码除了不够优雅外,编译时间也较长。修改为Switch case语句如下:


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

待优化代码示例 4:


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 来推断需要的是小数还是整数。修改后的代码如下,编译超时警告消失:


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。


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

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

Compilation Time

上面的ZLPhotoBrowser就是通过 Pod 方式安装的第三方库,可以看到该库的源文件编译时间为 30 秒... 在这个页面上可以详细查看每个库的编译时间以及整个项目的编译时间。针对编译时间较长的三方库,可以考虑改为使用Carthage方式导入,可以参考Carthage 的使用Carthage是一种将要使用的第三方库下载并编译为xcframework的方式,然后将生成的xcframework导入到项目中使用,因此每次进行 clean build 后,并不会重新编译,从而可以节省编译时间。

总结#

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

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

建议在进行优化之前,首先分析项目的编译时间卡在哪里,确定是三方库的编译时间过长还是项目源文件的编译时间过长,然后决定重点进行步骤二还是步骤三的优化。

参考资料#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。