- Analyzing and Improving Build times in iOS
- Debug-Mode.
- Release-Mode.
- [Analyzing the code using XCLogParser.](#analyzing-the-code-using-xclogparserhttpsgithubcommobilenativefoundationxclogparser)
- Code optimization
- Conclusions
Analyzing and Improving Build times in iOS
- 使用XCLogParser分析XCode项目的构建时间 .
- 使用[Sitrep]使用[Sitrep]分析Swift代码分析Swift代码.
- 我如何减少了40%的iOS项目调试构建时间!
- 一个Swift编译器错误是如何让我们的release -build花了大约50分钟(以及我是如何解决这个问题的)。
Debug-Mode.
几个月前,我开始着手减少项目的编译时间。我们花了大约6-8分钟在调试模式下编译一个干净的构建。每次我必须清除Derived Data文件夹并编译一个新的构建时,我都忍不住想我需要做些什么,因为这花费了太长时间。
这款应用依赖于Cocoapods管理的35个外部框架。它实际上只需要不到12个框架,但这些框架又有其他依赖关系,因此总数上升到35个。
Sitrep for some stats
为了让你了解我正在开发的应用的规模,我将分享一些我通过Sitrep收集的数据。Sitrep是一个由Paul Hudson构建的工具,它可以让你快速了解你的Swift项目代码统计。我用它来测量每个模块、应用程序和应用程序所依赖的Pods的代码行数。

Using Cocoapods
Cocoapods处理依赖关系的方式是check out源代码,并将其引入到我们的应用项目中。每当我们编译一个应用程序时,依赖项代码也会被编译。因此,项目需要大量的时间来构建每一个框架。
下图显示了构建报告的部分分析,它是用XCLogParse创建的, 关于用Cocoapods编译的应用程序。

我们该如何改进呢?每当我们需要编译我们的项目时,我们如何避免编译所有那些没有改变的框架?
一个答案是只编译这些外部依赖项一次,并使用那些预编译框架(动态库)。
Using Carthage and Pre-Compiled Frameworks.
使用Carthage,我们可以指定我们的依赖项并只编译一次,在应用程序目标的构建过程之外。一旦编译了依赖项,应用目标就会使用它们来构建、链接和运行。
编译发生在我们运行carthage bootstrap或carthage update 时。该工具将使用xcodebuild来编译每个框架公开的目标,结果是,我们将获得.framework对象。
因此,应用目标引用的是预编译框架,而不是源代码。当我们创建一个干净的构建时,XCode只编译应用程序的源文件,避免编译外部依赖。这节省了很多时间。
看看同一个应用的构建时间,使用的是由Carthage管理的预编译框架。
减少40%的时间!
What’s the reason behind this improvement? Simply, there’s less code to compile! The Pods were taking 44.4% of the app code. By migrating most of the dependencies to Carthage (I couldn’t migrate them all yet) the Pods percentage dropped to 11.5.

37% less code to compile! The total number of lines went from 187990 to 118037.
Future Improvements.
Pre-Compiled Modules. The architecture of the app in question, a modular approach described in this article I wrote, depends on all modules being compiled as part of the main target. 对于更大的项目,进一步的改进可以是分离这些模块,并将它们视为迦太基依赖关系。
这将需要预先编译每个模块。这将节省编译主要目标时的时间。这种方法可以使用git子模块来处理不同模块源代码中的变化。
Static Frameworks. 另一个改进是使用静态框架。即使这不会改善编译时,在理论上,它会改善应用程序的启动时间。
SPM maybe. 我还没有尝试过SPM。我认为同样可以用Swift Package Manager实现,也许配置项目的步骤更少。你试过吗?我们可以使用它的预编译框架吗?有什么想法吗?
Migrating from Cocoapods to Carthage.
移植过程相当简单,但很慢,需要大量的试验和测试。的确,Carthage需要更多关于Xcode、构建阶段、项目配置等方面的知识。但这并不难,我发现我在集成它的时候学到了不少东西。
例如,重要的是要记住,每个模块的框架搜索路径和主要目标包括Carthage/IOS/Build文件夹,还包括在Link Binary with Libraries构建阶段的框架和正确配置文档中描述的Carthage脚本。
有些依赖项在使用迦太基时很难集成,比如Firebase堆栈。其他依赖项没有Carthage的支持。所以,我们最终得到了一种混合方案,同时使用Carthage和Cocoapods。
Release-Mode.
在几个月的时间里,我们的项目花费了超过1.2小时的时间去编译并将其从Bitrise上传到TestFlight。我们一直不明白为什么会发生这种情况,我们认为这是Bitrise的问题。
整个Bitrise的发布过程花了1.5个小时!我们的项目在调试模式下进行编译大约需要6-8分钟。但是在Bitrise和Release模式下,我们发现编译需要45分钟。
Bitrise的一个很酷的特性是,你可以运行一个工作流,并远程查看运行它的虚拟机。通过这种方式,我能够进行更多的调查,并发现发生了什么。
经过几次尝试来减少编译时间,尝试Bitrise工作流,构建阶段脚本,从Cocoapods迁移到Carthage等等。我发现这道题比较简单。
发布-构建模式本身就是罪魁祸首。Carthage或任何后构建阶段脚本(Sourcery, SwiftLint等)甚至Bitrise都没有错。
所以,我发现了问题,发布模式构建花费的时间太长了。但是原因是什么呢?XCode的报告没有提供任何相关信息。我可以看到构建大约花费了45分钟,编译器挂起了大约10个文件。
即使在使用诊断标志进行编译以报告编译时间时,XCode报告也没有用。Xcode报告只显示了正在编译的文件列表,编译器挂起直到半小时后报告没有产生相关信息。
我的直觉是,发布模式下的代码优化可能是原因,事实证明,我是对的。我在调试模式下打开了代码优化编译器标志,并编译了项目。建造时间从6-8分钟增加到45分钟。
问题是代码优化。没有优化的编译时间:6-8分钟;优化后:45分钟!!
Analyzing the code using XCLogParser.
那么,是什么类、方法或函数破坏了编译器?我无法在XCode构建报告中找到这些信息。幸运的是我找到了XCLogParser。
该工具允许我查看哪个文件需要这么长的编译时间。这是一个巨大的进步。我分析了文件,最后,我发现了一个看起来可疑的结构上的switch语句,因为它非常大 我注释掉了那条语句,突然构建时间就正常了。
为了修复这个问题,我替换了switch的一些内部语句,在其他实体上调用==。我只使用标识符简化了代码,这似乎降低了代码优化的复杂性。
我不能模仿这个问题,把它变成一个更简单的形式,来复制在我看来像是编译器错误的东西。但我很高兴我解决了这个问题。
Code optimization
在这个项目中,我发现Swift编译器有两个问题,分别是代码优化和switch语句的处理。
在我这里提到的例子中,Swift编译器挂起了40分钟左右,试图优化一个枚举上的switch语句,该枚举包含另一个枚举的比较,该枚举包含另一个switch语句。
我无法在一个最小的例子中重现这些条件来理解问题到底是什么,但是在更改了引用内部结构的equals方法的switch语句之后,问题就解决了。
Conclusions
-
Sitrep 是一个小工具,允许我们快速分析项目的总体统计。你可以计算类、结构、代码行数等等。很高兴能时不时地看到你的项目在哪里。
-
XCLogParser
是一个分析构建的好工具. 理解我们的构建对于获得更好的编译时间至关重要。 -
Bitrise’s Build with Remote Access 是一个很好的工具,可以查看我们的CI构建的实时问题。
-
预构建框架产生更低的编译时间,当您需要对Cocoapods进行优化时,Carthage是一个很好的替代方案。
-
最后,在进行或不进行优化的代码编译方面存在差异。在我们的例子中,编译器在优化代码时在一个复杂的Swift语句上停止。这可能是由于编译器中的一个错误,我无法用一个更简单的情况来重现。