vlambda博客
学习文章列表

Xcode 构建优化全指南

作者 | Saru 
来源 | Medium,点击“阅读原文”查看作者更多文章

如果你正在寻求提高 Xcode 构建性能的方法,那你找对地方了。我看过很多博客或文章,很少能详尽说明如何加快 Xcode 构建速度,所以我决定整理一篇。

减少构建时间对于开发人员来说非常有意义,尤其是构建那种有大量依赖库的主传代码时。在本文中,我将尝试列出所有可能的方法,这些方法可以用来调整 iOS 项目以提高 Xcode 构建的性能。

这里先做一些必要的简要说明:

• Action Points - 可以在 Xcode 中执行的特定动作

• Real-time Effect - 我选择的一个非常棒的开源库 SwiftBlog,在此基础上进行优化,并实时查看效果。

事不宜迟,让我们开始吧。

Xcode 构建优化全指南

显示构建时间

首先,我们需要知道 Xcode 花了多少时间来构建我们的项目。默认情况下,Xcode 并不会显示构建时间,因此我们必须启用这项功能。

Action Points

1、在终端运行以下命令

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

2、现在,在构建项目时,就会在 Xcode 的构建条中显示构建时间了。

注意点 1:如果 Xcode 由于某种原因未显示,那么尝试重新启动 Xcode

注意点 2:为了显示正确的构建时间,先确保深度清理项目,也就是说需要清除 Build 文件夹(Command + Shift + K)的所有数据。

能够获取构建时间后,我们就可以来实际调整设置,以提高构建过程的性能了。

构建系统

Xcode 9 在其预览版中引入了一个新的构建系统。而到了 Xcode 10,新的构建系统则成了默认设置。这个新的构建系统的主要目标就是要减少总体的构建时间。

Action Points

要使用新的构建系统,可以在 File -> Workspace/Project Settings 中启用它。

为了了解其实际效果,我们可以尝试使用新旧两种构建系统分别来构建项目,以对比两者的构建时间。虽然在我们的示例项目中,两者差别不是很明显,但在重型项目中会有明显的区别。如果有条件,可以在那些有大量依赖关系老项目中试一试。

Reat-time Effect

现在,让我们基于 SwiftBlog,来使用两种构建系统对其进行构建。

Xcode 构建优化全指南

Xcode 构建优化全指南

看到差别了吗?0.991秒(旧版为4.223秒,新系统为3.232秒)。这个实例并没有任何相依,但仍然相差约1秒,效果还是很显著的。

当然,这只是 Xcode 本身的默认优化。接下来,我们来看看可以通过调整哪些项目设置来减少构建时间。

架构影响

在开发过程中,我们更关注构建时间,这实际上会占用我们的大部分时间。当以调试模式来构建项目时,最好只针对特定的架构来构建。虽然这是 Xcode 的默认选项,但以防万一,最好先确认一下。

设备和模拟器有不同的架构,但通常在开发过程中,我们希望 Xcode 或者在设备或者在模拟器上运行。所以,设置 Build Active Architecture Only 为 Yes ,来要求编译器仅生成一种架构的二进制文件。

Action Points

在项目的 Build Settings -> Build Active Architecture Only 中,将 Debug 设置为 Yes ,将 Release 设置为 No 。

让我们在 SwiftBlog 上将 Debug 设为不同的值,看看效果。

Xcode 构建优化全指南

Xcode 构建优化全指南

可以看到两者相差 3.385s。

注意,我们在这里使用的是旧版构建系统,如果使用新的构建系统,同样会有巨大差异。

编译模式

Compilation Mode 告诉编译器是构建项目中所有文件还是仅构建修改后的文件。 Incremental (增量)表示仅编译已修改的文件, Whole Module 表示不考虑修改而构建项目中所有文件。

1、进行 Build Settings -> Compilation Mode 。默认情况下,Xcode 10+ 是将其设置为 Incremental(Debug) 和 Whole Module(Release) 。

2、如果项目中的构建设置和上面相同,那么是 OK 的,否则,修改你的设置。

现在,让我们在 SwiftBlog 项目中配置该荐为不同设置,来看看实际效果。

注意:需要通过以下步骤来看看这项优化:

1、构建一次项目

2、做一些修改

3、再次构建项目,这时就能看到 Compilation Mode 的效果了。

Xcode 构建优化全指南

Xcode 构建优化全指南

在这个实例中,你可能会发现和预期不一样。确实,在 SwiftBlog 这个小项目中,最好还是选择 Whole Module ,因为编译器会省略检测更改文件的过程,因此反而能节省一些时间。但是在有很多依赖的项目中, Incremental 无疑是最佳选择。

优化级别

Optimization Level 告诉编译器将构建优化到某个级别。通常,Debug 的构建设置为 No Optimization ,这样可以调试通过 let/var 包含的值。这在调试阶段是非常有必要的。

1、进入 Build Settings -> Optimization Level ,然后查看设置的值

2、如果不进行大量调度,最好将其设置为 Optimize for Speed ,由于编译器将省略将值附加到调试器线程的步骤,因此将减少构建时间。

注意:还有另一个设置,即 Optimize for Size ,通常是指机器代码生成的整体大小,而不是应用程序的大小。鉴于此,我们主要关注减少本文讨论的“构建时间”,因此忽略这一项。

让我们将些配置应用于 SwiftBlog 项目。

注意:应该按照 Compilation Mode 中提到的相同步骤来操作,以查看效果

Xcode 构建优化全指南

Xcode 构建优化全指南

这里只有 0.2s 的微小差异,但是它将对大型项目产生重大影响。

依赖管理

iOS有2个主要的依赖管理器:Carthage和Cocoapods。

两者都有各自的优点和缺点。关于孰优孰劣,已经有很多争论了,不过本文不会对些深入探讨。我们来看看它们与构建过程的关系。

通常,在大多数项目中,一旦集成了依赖库 lib/module/framework,我们就希望不更改这些库的源码。即使要修改,将这些修改集中在独立的 repo/module/framework 中,然后将特定 module/framework 集成到项目中,始终是一种最佳实践。基本思想是在模块下的情况下,尽可能地分离 module/framework。

静态/动态 framework 在将任何第三方库集成到我们的 iOS 项目中时非常有用,因为一旦生成 framework,就不会在项目中重新编译。这可以节省大量时间,因此,这也是一些人选择 Carthage,而不是 Cocoapods 的原因之一。

当使用 Cocoapods 来管理依赖项时,每次编译工程,所有的依赖项都会被编译。而如果使用 Carthage,在添加依赖项时,依赖库只被编译一次。它会生成一个 framework,并链接到工程,在构建工程时,不会再去编译依赖项。

我们将看看如何让编译器并行执行构建,该构建与项目中定义的依赖关系紧密相关。

1. 构建的机器内核数

先决条件—查看Mac的内核数量

1、在终端运行以下命令:

sysctl -n hw.ncpu

2、它会输出一个整数,表示计算机的内核数:

4 // 这是我的内核数

有 12 个内核的 iMac 这样的机器运行相同构建的速度比我的快 3 倍。因此,计算机的内核数起着很大的作用,它与构建执行时间成正比。

默认情况下,Xcode 使用与计算机 CPU 内核数相同的线程数。但是,调整线程数,使用更多的线程,可以显著减少构建时间(在某些情况下可以减少 30%)。这个过程利用了处理器处理多线程或模拟额外内核的能力。需要注意的是,你需要通过尝试来确定代码并行构建的收益情况,然后相应地调整线程数。

修改线程数

让我们尝试修改线程数。从终端运行以下命令:

默认写入com.apple.Xcode PBXNumberOfParallelBuildSubtasks 8

接下来是使并行构建执行更简单的方法。这是通过Xcode UI本身完成的,后者会自动执行此优化。

2. 执行并行构建

我们将研究如何在Xcode中启用并行构建执行。

Action Points

导航到 Edit Scheme -> Build ,确保勾选 Parallelize Build 和 Find Implicit Dependencies 。

Build Hypothesis

Xcode 编译器获取项目的源代码,并构建一个树状结构来定义模块的依赖关系。然后采用自下而上的方法进行编译,即首先编译最小依赖性的模块。

通常,Xcode 项目会依赖于其它 framework 或 target。例如,当一个项目添加了 Target Dependencies 时,编译器首先要确保这边链接的目标 framework/module 先被编译。同时,在执行 test target 时,编译器首先确保 app target 被编译。因此,依赖关系在使编译器以最佳方式执行并行构建任务中起着重要的作用。在这里,我们可以做的是:

• 按执行顺序列出所有依赖项

• 按执行顺序列表项目中的所有 target

• 减少依赖

• 解耦或模块化代码库,以生成独立的模块

尽管依赖关系始终由编译器在内部进行标识,但最好将它们按照执行顺序放置。这最终可以帮助编译器在构建过程中,减少内部重新排序依赖的时间。

正确处理完后,这将让构建性能提高 2 ~ 3 倍,当然这取决于项目的复杂性。

Real-time Effect

对于没有或很少依赖项的小型业余项目来说,结果可能适得其反,即并行构建实际上会增加构建时间。我们可以在 SwiftBlog 项目上观察到这一点。

但是,对于有大量依赖关系的实际项目,最佳做法是使用并行构建。

关于依赖关系排序的另一个重要特征是链接框架,我们下面来讨论。

链接框架

Link Frameworks Automatically 表示添加到项目中的任何 framework 都应自动链接并由编译器在构建过程中考虑使用。

Xcode 默认将 Link Frameworks Automatically 设置为 Yes 。

但是,苹果并不保证这一点,而是希望开发人员将任何外部 framework 链接到 Build Phases -> Link Binary -> With Libraries 或 Target Dependencies 。我们不必对 UIKit 、 Foundation 这些隐式依赖项执行此步骤。

因此,这里的总体目标是对于外部 framework 不要过多依赖 Link Frameworks Automatically。最好在构建阶段中手动链接依赖项。

识别耗时的代码

Xcode 允许我们识别导致编译时间严重滞后的代码块。你可以为代码块执行指定一个时间限制,交让 Xcode 对超出指定时间限制的代码给出警告。

Action Points

1、导航到 Build Settings -> Other Swift Flag ,并添加以下标记:

-warn-long-function-bodies=200

-warn-long-expression-type-checking=200

整数值 200 代表毫秒数。因此,在执行构建后,Xcode 将对任何执行时间超过 200 毫秒的函数或表达式给出警告。

2、添加这些标志后,无论何时构建项目,Xcode 都应该显示警告。

当你需要确定消耗比预期时间更多的那些函数或表达式时,这非常有用。

dSYM的影响

dSYM 文件对崩溃报告文件的去符号化过程非常有用。编译器需要一些时间来生成 dSYM 文件。仅当没有附加 Xcode 调试器时,启用它才有意义。因此,最好在模拟器上运行时将其禁用。

Action Points

1、导航到 Xcode 的 Build Settings -> Build Options 

2、在 Debug Information Format 下,设置值 为 DWARF(Debug) 和 DWARF with dSYM File(Release) 

这个设置告诉编译器在调试模式下省略创建 dSYM 文件的过程。这在一定程序上能节省构建时间。

Real-time Effect

让我们在 SwiftBlog 中配置一下

可以看到,两者相差 0.6s。

Objective-C/Swift 互操作性

随着 Swift 的到来,出现了混合开发,工具的一部分旧代码库已迁移到最新语言,而另一部分继续使用Objective-C。最终要求开发人员以某种方式处理互操作性。

为了实现互操作性,我们必须处理两种类型的头文件:

• Bridging Header -- 包含应公开给 Swift 的所有 Objective-C 接口

• Generated Header -- 包含所有应公开给 Objective-C 的 Swift 接口

这些头文件定义了两种语言编写的文件的依赖性。这两种语言的文件之间的互操作性是通过 Bridging/Generated Header 实现的。对头文件的更改将极大地影响构建时间。例如,Swift 接口文件中的微小变化会让编译器重新编译所有引用了该文件的Objective-C文件,反之亦然。

所以,最好只在两种语言的文件中同时公开所需的接口。通过添加访问修饰符:private/protected,避免添加/导入不必要的文件。

减少 UITests 执行时间

这听起来有些怪异,因为从技术上讲这没有意义,但确实如此。

在运行 UI 测试用例时,请尝试将模拟器的物理和像素窗口大小调整为尽可能最小。然后,可以看到 UITest 用例执行时间的一些改善。

发生这种情况是因为 Xcode 运行 UI 测试用例时,通常会在模拟器上安装并运行实际的应用程序以执行任何给定的UI测试用例。这意味着它实际上就是使用该应用程序的真实用户。因此,就像使用该应用程序的真实用户一样,在运行UI测试用例时,会消耗系统内核/资源。

因此,始终建议将模拟器设置降至最低,以减少对系统资源的使用。这最终将加快测试用例的执行速度,并减少总体执行时间。

结论

我们看到了许多选项设置,以提高 Xcode 构建性能。Xcode确实为我们提供了多种可配置选项的组合,但是由我们来确定哪种组合是最有效的并且最适合特定项目的。但是,只有通过尝试各种组合的选项才能发现这一点。


推荐阅读

• 
• 
• 
• 
• 


就差您点一下了 👇👇👇