(原文版本:Commits on Dec 11, 2017 ce6da1f3a47220259c3924df62f44f06bc45e222
翻译:Yuen,博客地址。如果对翻译质量存疑,欢迎提出问题建议)
Swift 不停在改进 ❤️。然而目前,对于中大型项目而言,漫长的编译时间仍是一个巨大问题。这个仓库的目的就是搜集相关 Tips,帮你减少项目的编译时间。
👷🏻 维护者:Arek Holko. 少了什么? 欢迎提 Issues 或者 PR!
Table of Contents
- 1、函数和表达式的类型检测(Type checking of functions and expressions)
- 2、编译缓慢的那些文件(Slowly compiling files)
- 3、Debug 时只编译 active 架构(Build active architecture only)
- 4、生成 dSYM(dSYM generation)
- 5、Module 优化(Whole Module Optimization)
- 6、CocoaPods 的 Module 优化(Whole Module Optimization for CocoaPods)
- 7、第三方依赖(Third-party dependencies)
- 8、模块化(Modularization)
- 9、XIBs
- 10、Xcode Schemes
- 11、使用全新的 Xcode 编译系统(Use the new Xcode build system)
- 12、启用 Concurrent Swift Build Tasks(Enable Concurrent Swift Build Tasks)
- 13、在 Xcode 中显示编译时间(Showing build times in Xcode)
1、函数和表达式的类型检测(Type checking of functions and expressions)
在 Build Settings --> Other Swift Flags 中加入
-
-Xfrontend -warn-long-function-bodies=100
(100
意味着 100 毫秒, 这个数字具体设置多少要依电脑配置和项目大小而定,建议多调整几遍找到大小相对合适的数字,ps: 1 秒= 1000 毫秒) -Xfrontend -warn-long-expression-type-checking=100
编译后会看到如下图所示的场景:
[图片上传失败...(image-40c51f-1514117379893)]
📖 Sources:
- Guarding Against Long Compiles
- Measuring Swift compile times in Xcode 9 · Jesse Squires
- Improving Swift compile times — Swift by Sundell
- Swift build time optimizations — Part 2
2、编译缓慢的那些文件(Slowly compiling files)
上一段针对的是函数级别和表达式级别,现在我们关注整个文件的编译时间
因为这里我们必须用 CLI 命令行界面来编译项目,所以务必将参数设置正确了:
xcodebuild -destination 'platform=iOS Simulator,name=iPhone 8' \
-sdk iphonesimulator -project YourProject.xcodeproj \
-scheme YourScheme -configuration Debug \
clean build \
OTHER_SWIFT_FLAGS="-driver-time-compilation \
-Xfrontend -debug-time-function-bodies \
-Xfrontend -debug-time-compilation" | \
tee profile.log
上述代码针对的是 .xcodeproj
的项目,如果项目是 .xcworkspace
的话,把上面第二代码中的
-project YourProject.xcodeproj
替换成为 -workspace YourProject.xcworkspace
。
然后从之前生成的 profile.log
文件中提取出编译所需时间。
awk '/Driver Compilation Time/,/Total$/ { print }' profile.log | \
grep compile | \
cut -c 55- | \
sed -e 's/^ *//;s/ (.*%) compile / /;s/ [^ ]*Bridging-Header.h$//' | \
sed -e "s|$(pwd)/||" | \
sort -ReactNative | \
tee slowest.log
成功执行之后,就会得到一个 slowest.log
文件,内容格式如下:
2.7288 ( 0.3%) {compile: Account.o <= Account.swift }
2.7221 ( 0.3%) {compile: MessageTag.o <= MessageTag.swift }
2.7089 ( 0.3%) {compile: EdgeShadowLayer.o <= EdgeShadowLayer.swift }
2.4605 ( 0.3%) {compile: SlideInPresentationAnimator.o <= SlideInPresentationAnimator.swift }
📖 Sources:
3、Debug 时只编译 active 架构(Build active architecture only)
默认就是这个设置,不过安全起见,可以去在 Build Settings --> Build active architecture only 确认一下
[图片上传失败...(image-b98285-1514117379893)]
📖 Sources:
4、生成 dSYM(dSYM generation)
新项目的默认设置是,Debug 配置编译时不生成 dSYM 文件。但有时候为了在开发时进行 Crash 日志解析,会去修改这个参数。生成 dSYM 会消耗大量时间,可以去确认一下。
📖 Sources:
5、Module 优化(Whole Module Optimization)
另一个公认的方法是
- 修改 Debug 配置
Build Settings
-->Optimization Level
为Fast, Whole Module Optimization
-
只在 Debug 配置添加
-Onone
flag 到Build Settings
-->Other Swift Flags
[图片上传失败...(image-826b1e-1514117379893)]
这告诉了编译器哪些东西?(译者注:这里感觉自己的翻译不到位,放原文吧)
It runs one compiler job with all source files in a module instead of one job per source file
Less parallelism but also less duplicated work
It's a bug that it's faster; we need to do less duplicated work. Improving this is a goal going forward
📖 Sources:
- Developear - Speeding Up Compile Times of Swift Projects
- Slava Pestov on Twitter: “@iamkevb It runs one compiler job with all source files in a module instead of one job per source file”
6、CocoaPods 的 Module 优化(Whole Module Optimization for CocoaPods)
如果使用 CocoaPods 的话,上一步 WMO 的配置也要考虑放到使用的 CocoaPods 项目中。
要实现 WMO 配置,需要在项目的 Podfile
文件中添加如下的 post_install
hook:
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if config.name == 'Debug'
config.build_settings['OTHER_SWIFT_FLAGS'] = ['$(inherited)', '-Onone']
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Owholemodule'
end
end
end
end
7、第三方依赖(Third-party dependencies)
在项目中有两种途径可以引入第三方依赖:
- 源码形式,在你每次 clean 项目编译内容的时候都会都会重新编译,例如:CocoaPods,git submodules, 复制粘贴的 code, 应用 target 依赖的子项目(
subprojects
)形式 - 提前编译好的 framework/library形式,例如:Carthage, 第三方不透露源码、提供的静态库
因此在你每次 clean build 之后,CocoaPods 大多情况下会重新编译,导致大量的编译时间花销。
Carthage 虽然难用一些,但当你很在意编译时间的话却不失为一个好选择。只有当你在更新依赖列表(添加新 framework,更新 framework 的新版本)的时候,你才会去编译外部依赖。Carthage 也许需要你花 5-15 分钟的时间去完成,但相比使用 CocoaPods 做依赖,单从长远的编译时间花销来看,Carthage 或许会节省不少时间。
📖 Sources:
- time spent waiting for Xcode to finish builds 😅
8、模块化(Modularization)
Swift 的增量编译并不完美。有时一些增量编译中,也许仅仅是修改了一个字符串,就会导致整个项目重新编译。这是一个亟待解决的问题。
为了避免这个问题,你可以考虑将 app 拆分为一个个模块。在 iOS 里,有2中方案:动态库和静态库(Xcode9 Beta4 版本开始支持 Swift 静态库)。
假设你的 app 依赖一个叫做 DatabaseKit
的内部 framework。模块化的方法能够保证在你对 app 项目做了一些修改时,DatabaseKit
不会因为这个增量编译的行为而重新编译。
📖 Sources:
9、XIBs
代码 和 XIBs/Storyboards 的取舍一直是个热门话题。不过我们这里只是片面的谈一谈这个问题。这个情况很有趣:当你在 IB 中做了一些改动时,只有这个 IB 文件被编译了(成为 NIB 格式);与之成鲜明反差的是,有时候当你在 UIView 的一个子类中修改了一行代码,Swift 编译器可能会重新编译项目中的很大一部分代码。
📖 Sources:
10、Xcode Schemes
假设我们有一个常见设置的项目,它有3个 target:
App
AppTests
AppUITests
只在一个 scheme 上工作没有问题,但是我们还可以优化。下方的配置是我们一直在使用的,包了3个 scheme:
App
⌘+B 只会编译这个 app。只跑单元测试不跑 UI 测试。 对于 short iterations 很有用, 比如在 UI 代码上, 因为只有需要用到的代码被编译了。
[图片上传失败...(image-59dd50-1514117379893)]
App - 单元测试工作流(App - Unit Test Flow)
编译内容会包含这个 app 和单元测试 target。运行的话,只会跑单元测试。在涉及到单元测试的代码时很有用,因为只要一编译完项目,就能立即发现在测试里的编译错误了。甚至不需要去运行他们。
当你的 UI 测试需要话费很久的时候,这个Scheme很有用。
[图片上传失败...(image-efc09e-1514117379893)]
App - 所有测试工作流(App - All Tests Flow)
编译应用和所有 target。跑所有测试用例。当涉及到那些和 UI 关系紧密包含 UI 测试的代码时作用较大。
[图片上传失败...(image-7e5e00-1514117379893)]
📖 Sources:
11、使用全新的 Xcode 编译系统(Use the new Xcode build system)
在 Xcode 9 苹果 介绍了一种新的 Xcode 编译系统. 这还只是“预览”版本,默认并没有开启。这个新系统比默认的原有的编译系统快的多。
如果想要使用它,到 Xcode 的 File
菜单进入Workspace 或 Project Settings
的页面,就可以切换到新的编译系统了。
[图片上传失败...(image-577790-1514117379893)]
📖 Sources:
12、启用 Concurrent Swift Build Tasks(Enable Concurrent Swift Build Tasks)
Xcode 9.2 对于增加 Swift 项目的 concurrent build tasks
有一个实验性质的支持。对于一些项目,这个特性可能会大大改善编译时间。另外要注意,当启用这个选项的时候,Xcode 可能会增加大量内存开销。
如果要启用这一特性,退出 Xcode,然后在 Terminal 窗口中输入以下命令:
$defaults write com.apple.dt.Xcode BuildSystemScheduleInherentlyParallelCommandsExclusively -bool NO
测试以下这个改动对你的项目编译产生了什么影响。对于很多项目来说,这不会导致什么变化;但是对另外一些,这个改动非常重要。如果要弃用这个特性的话,在 Terminal 窗口中输入以下命令,然后重启 Xcode:
$defaults delete com.apple.dt.Xcode BuildSystemScheduleInherentlyParallelCommandsExclusively
(译者注:为什么是设为 NO 而不是 YES,开发者在 推特上 说是当时写错了😂。值得注意的是,根据这里所说,开启这个新特性的操作,只会影响原有的默认编译系统 standard build system
;新的 build system 已经启用这一特性。
📖 Sources:
13、在 Xcode 中显示编译时间(Showing build times in Xcode)
最后,为了能够准确知道你的编译时间是否改善了,你应该在 Xcode 的 GUI 界面中显示时间。在命令行中运行:
$ defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES
成功之后,在 ⌘+B
编译一个项目后你会看到:
[图片上传失败...(image-a28378-1514117379893)]
建议每次都在一个相对公平的环境下比较编译时间,比如:
- 退出 Xcode
- 清理 Derived Data (
$ rm -rf ~/Library/Developer/Xcode/DerivedData
) - 打开 Xcode 中的项目
- 在 Xcode 打开或者完成索引阶段(indexing phase)后,立刻开始编译。因为使用 Xcode 9 编译也会执行索引,所以
The first approach
看起来更具代表性。(The first approach seems to be more representative because starting with Xcode 9 building also performs indexing)
另外,你也可以利用命令行统计编译时间:
$ time xcodebuild other params
📖 Sources: