Swift 编译时间折半优化
2020-03-30 本文已影响0人
郑一一一一
前言
在 Swift 项目过程中,随着代码量的不断增加,每次调试的编译速度越来越慢,在 debug 下编译项目竟然需要 6 min 以上。遂开始研究如何缩短编译时长。
如何获取精确的编译时长?
编译前,请执行以下两个步骤:
- 清空 derived data 文件夹中所有文件(推荐一个清理工具
Cleaner for Xcode
,可以在 Mac App Store 搜索下载到) - 在
Xcode
中 clean(快捷键:cmd+shift+k) - 能够让 Xcode 显示编译时间,终端输入
// Xcode 显示构建时长设置
// 在 terminal 中输入
defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES
Xcode 配置优化
// 1
Build Settings -> Swift Compiler - Code Generation -> Optimization Level 下 Debug 修改为 None
// 2
Build Settings -> User-Defined 下 Debug 增加 SWIFT_WHOLE_MODULE_OPTIMIZATION = YES,其它诸如 Release 配置请使用 NO
// 3
Build Settings -> Build Options -> DEBUG_INFORMATION_FORMAT 下 Debug 修改为 DWARF
// 4
Build Settings -> Architectures -> Build Active Architecture Only 下 Debug 改为 YES
关于 SWIFT_WHOLE_MODULE_OPTIMIZATION
和 Optimization Level
的区别,以下链接有详细说明。简单来说前者是从全局层面对所有代码进行优化,而后者是针对每个 file 的优化。
What is the difference between User Defined SWIFT_WHOLE_MODULE_OPTIMIZATION and Swift Optimization Level?
代码优化
代码层面的优化,我们可以依靠 Xcode 找出耗时较长的方法,进行针对性优化。
配置
Xcode -> Build Settings -> Swift Complier - Custom Flags
Debug 下增加
// 1 方法体时长超过 100 ms
-Xfrontend -warn-long-function-bodies=100
// 2 类型推断时长超过 100 ms
-Xfrontend -warn-long-expression-type-checking=100
添加如上配置以后,只要时长超出100 ms,Xcode 中便会显示 warning。这里的时长可以根据实际情况进行调整。
Xcode层面修改
代码层面修改
针对产生 warning 的代码处,我们就可以根据下面进行针对性修改啦。
1 指明变量类型
普通变量
// 修改前
var name = getName()
// 修改后
var name: String = getName()
数组
// 修改前
let array = ["a", "b", "c", "d", "e", "f", "g"]
// 修改后
let array: [String] = ["a", "b", "c", "d", "e", "f", "g"]
闭包
// 修改前
let sum = [1, 2, 3].map { String($0) }.flatMap { Int($0) }.reduce(0, +)
// 修改后
let sum = [1, 2, 3].map { (num: Int) -> String in String(num) }.flatMap { (str: String) -> Int? in Int(str) }.reduce(0, +)
2 拆分 chain closure
// 修改前
let sum = [1, 2, 3].map { String($0) }.flatMap { Int($0) }.reduce(0, +)
// 修改后
let numbers = [1, 2, 3]
let stringNumbers = numbers.map { String($0) }
let intNumbers = stringNumbers.flatMap { Int($0) }
let sum = intNumbers.reduce(0, +)
3 避免空合运算符 ??
可以使用 if let 来代替
// 修改前
let name = string ?? ""
// 修改后
if let name = string{
/* string has value */
}else{
/* string is nil*/
}
4 避免三元运算符 ?
// 修改前
let letter = isFirst ? "a" : "b"
// 修改后
var letter = ""
if isFirst {
letter = "a"
} else {
letter = "b"
}
5 不要使用 + 来拼接字符串、数组
字符串
// 修改前
let url = "https://google.com/" + "path/" + "anotherpath"
// 修改后
let url = "https://google.com/\("path")/\("anotherpath")"
数组
// 修改前
let systemNames = (0...2).map{ String(format: localizedFormat, systemOptions[$0]) } + [NSLocalizedString("everything", comment: "")]
// 修改后
var systemNames = systemOptions.dropLast().map{ String(format: localizedFormat, $0) }
systemNames.append(NSLocalizedString("everything", comment: ""))
6 避免 if else if 中做太多运算
// 修改前
if number == 60 * 60 {}
// 修改后
let number: Double = 60 * 60
if number == 3600 {
}
7 避免无意义的类型转换
// 比如变量 a 已经是 CGFloat 类型,不要再强转 CGFloat(a)
// 修改前
return CGFloat(M_PI) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180
// 修改后
return CGFloat(M_PI) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180
8 使用 let private final
9 使用自定义运算符(尤其是含有泛型参数)
结果
最终通过 Xcode 和代码两个层面的优化,编译时间从原先的 360 s 左右缩减到 190 s。哇哦!