今是昨非

今是昨非

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

iOS 编译时间优化

背景#

目前 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 是自己设定,可针对项目的真实情况设置:


-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 显示编译时间,打开 terminal,复制下面,去运行,重启 Xcode。


$ 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方式导入,可以减少重新编译时间。

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

参考#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。