[译]优化 Swift 的构建时间

2,502 阅读3分钟

原文链接:Optimizing-Swift-Build-Times

原作者:fastred

欢迎各位 点赞评论,感觉有用的朋友可以关注笔者公众号 iOS 成长指北,持续更新

本文是对原文的更新,增加了笔者经验判断部分,如有错误,欢迎指正

前言

在阅读 How Uber Deals with Large iOS App Size[1] 开篇了解到一个关于 -Osize 标记的优化,在 GitHub 搜索时,发现一个 Optimizing-Swift-Build-Times[2] 的项目,翻译一下其中的优化点,希望能够为读者提供一些帮助

注意 本优化主要是针对 Swift 部分

增量编译模式

在 Xcode 10 之前,加快 Debug 构建的速度很常见的方案是启用 Whole Module优化。

目前,推荐的设置为 Compilation Mode 在 Debug 构建时设置为 Incremental,当 Release 构建时则设置为 Whole Module。同样,Debug 构建下的 Optimization Level 应该设置为 No Optimization

compilation-and-optimization@2x.png

函数和表达式的类型检查

通常 Swift 构建时间很慢,主要是因为进行了类型检查。默认情况下,Xcode 不会显示编译缓慢的代码。你可以通过添加以下设置找到那些构建缓慢的函数和表达式:

  • -Xfrontend -warn-long-function-bodies=100 (100在这里意味着100毫秒,你需要根据实际的项目和设备来更精确的获取值)
  • -Xfrontend -warn-long-expression-type-checking=100

在构建设置中设置 Other Swift Flags

times@2x.png

重新构建,现在应该看到类似以下的警告:

xcode-warning@2x.png

下一步就是解决这些编译过程中有问题的代码。你可以通过参考资料中[3][4][5][6]来找到一个方案。

编译缓慢的文件

除了那些构建缓慢的函数和表达式以外,了解整个整个文件的编译时间,也是一个很有趣的体验。Xcode 没有对应的可视化工具和相应的设置,所以你必须在 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

如果你的构建项目是 workspace 的话,你可以将-project YourProject.xcodeproj 替换为 -workspace YourProject.xcworkspace

请注意本文是撰写于 2018-2019年,有些配置项比较老旧,请针对进行修改

然后使用以下方法提取统计数据:

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 -rn | \
  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 }

你可以阅读参考资料 [7] 来进行更进一步的了解。

Build active architecture only

此设置是默认设置,但你应仔细检查它的是否争取。项目应仅在 Debug 构建时将 Build active architecture only 置为 YES。

active-arch@2x.png

关于什么是 Build active architecture only , 你可以阅读下面的资料 [8]

有条件的生成 dSYM 文件

默认情况下, 在 Debug 模式下我们默认不开启 DWARF with dSYM File, 不过在非调试状态下出现的崩溃又很有用。一个简单的例子就是你打给测试的测试包崩掉了...

基于现在大部分公司都自动构建项目分发给测试,你可以在调试时进行选择不生成 dSYM 文件来加快你的编译速度。

dsym@2x.png

关于这部分的详细资料,你可以参阅推荐参考[9]

第三方依赖

常见的向项目中嵌入第三方依赖的方法有三种:

  • CocoaPods:作为每次执行项目的 clean build 时所编译的源文件
  • Carthage:作为一个预先构建的框架/库
  • Swift Package Manager:苹果提供的添加第三方依赖的方法,旨在简化共享代码和重用他人代码的过程。

CocoaPods 是 iOS 上最受欢迎的依赖管理器,从设计上来说,这会导致更长的编译时间,因为在大多数情况下,每当你执行一个 clean build 时,第三方库的源代码都会被编译。通常情况下你不需要经常这么做,但在现实中,你确实有这么做的需要(例如因为分支切换、Xcode bug等)。

Carthage 使用成本比较高,但如果关注于构建时间的话,它是一个更好的选择。仅当更改依赖项列表中的某些内容(添加新框架,将框架更新为较新的版本等)时,才构建外部依赖项。这可能需要 5 到 15 分钟才能完成,但是与构建嵌入 CocoaPods 的代码相比,这样做的频率要少得多。甚至可以使用 Rome[10] 跳过最初的几分钟。

Swift Package Manager 几乎和源码一样的编译速度,但是你依赖的文件不包含任何 Objective-C 或捆绑资源。对自己使用的项目来说,这可能是一个很好的选择。

模块化

Swift中 的增量编译并不完美。在某些项目中,在某个位置更改一个字符串会导致几乎整个项目在增量构建过程中重新编译。你可以对它进行调试和改进,但尚无一个好的工具。

为避免此类问题,你需要考虑将应用拆分成多个模块。在 iOS 中,它们是:动态框架或静态库(在 Xcode 9 中添加了对 Swift 的支持)。

假设你的应用目标依赖于一个叫做 DatabaseKit 的内部框架。使用这种方法主要是为了保证,当你在应用程序项目中更改某些内容时,在增量构建期间不会重新编译 DatabaseKit

阅读参考[11][12]对你会有帮助的。

XIBs

使用XIBs/storyboards 还是 纯代码编写界面是一个需要权衡取舍的问题,这里我们不继续讨论。但是有趣的是,当你在 Interface Builder 中更改文件的内容时,只会编译该文件(为NIB格式)。而另一方面,另一方面,当你更改(例如UIView子类中公共方法中的一行)时,Swift 编译器可能会决定重新编译项目中很大一部分。

如果你对这方面有疑问,你可以阅读参考资料[13]

Xcode Schemes 配置

假设我们有一个具有 3 个 Target 的公共项目设置:

  • App
  • AppTests
  • AppUITests

仅使用一种方案也是可以的,但是我们可以做得更好。我们最近使用的设置包含三种方案:

App

cmd-B 构建应用程序时,只运行单元测试。这对于短迭代很有用,例如在 UI 代码上,因为只构建了需要的代码。

app-scheme@2x.png

APP-单元测试流程

构建应用程序和单元测试目标时,仅运行单元测试。当处理与单元测试相关的代码时非常有用,因为你在构建项目后立即发现测试中的编译错误,甚至不必运行它们!

当 UI 测试太长而无法经常运行时,此方案非常有用。

app-unit-test-flow@2x.png

APP-所有测试流程

构建应用程序和所有测试目标,运行所有测试。在处理影响 UI 测试的 UI 代码时很有用。

如果你对 Xcode Schemes 方面的配置有所疑问,你可以查看参考资料[14]

app-all-tests-flow@2x.png

使用 new Xcode build system

在 Xcode 9 中,苹果引入了一个新的构建系统。要启用它,请在Xcode 菜单栏 -> File -> Workspace/Project Setting。将 Build system 改为 New Build System

new_build_system@2x.png

这在 Xcode 10 中这个是默认的。不过这个可能会导致一个 Cycle in dependencies between targets... 的错误,在你使用 CocoaPods 添加依赖项的时候。

你可以查看参考资料[15]来了解他的更多信息

在 Xcode 中显示构建时间

最后,要真正知道你的构建时间是否真有所改善,你应该启用在 Xcode 的用户界面中显示构建的时间。为此,请在命令行工具中运行此命令:

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

完成后,在构建项目(cmd-B)后,你应该看到:

time@2x.png

我建议每次都在相同条件下比较构建时间,例如:

  • 退出 Xcode
  • 清除 Derived Data $ rm -rf ~/Library/Developer/Xcode/DerivedData
  • 在 Xcode 中打开你的项目
  • 在 Xcode 打开后或索引阶段完成后立即启动生成。第一种方法似乎更具代表性,因为从Xcode 9开始,构建也会执行索引。

或者,你可以使用命令行查看构建时间:

$ time xcodebuild other params

你可以在参考资料[16]获取如何查看构建时间。

参考资料

  1. How Uber Deals with Large iOS App Size:eng.uber.com/how-uber-de…
  2. Optimizing-Swift-Build-Times:github.com/fastred/Opt…
  3. Guarding Against Long Compiles:khanlou.com/2016/12/gua…)
  4. Measuring Swift compile times in Xcode 9 · Jesse Squires:www.jessesquires.com/blog/measur…
  5. Improving Swift compile times — Swift by Sundell:www.swiftbysundell.com/posts/impro…
  6. Swift build time optimizations — Part 2:medium.com/swift-progr…)
  7. Diving into Swift compiler performance:koke.me/2017/03/24/…
  8. What is Build Active Architecture Only:samwize.com/2015/01/14/…
  9. Speeding up Development Build Times With Conditional dSYM Generation:holko.pl/2016/10/18/…
  10. Room: github.com/tmspzz/Rome
  11. Technical Note TN2435 – Embedding Frameworks In An App:developer.apple.com/library/con…
  12. uFeatures:github.com/microfeatur…
  13. (…) in a large project incremental build is much faster if only a .xib was changed (vs. only a line of Swift UI code:twitter.com/MichalCiuba…
  14. All About Schemes:pilky.me/17/
  15. Faster Swift Builds with the New Xcode Build System:github.com/quellish/Xc…
  16. How to enable build timing in Xcode? - Stack Overflow:stackoverflow.com/a/2801156/1…

如果你有任何问题、评论或反馈,请随时联系。如果你愿意,可以通过分享这篇文章来让更多的人发现它。

感谢你阅读本文! 🚀