xcode优化编译时间

[TOC]

Xcode 优化编译时间设置

显示构建时间

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

1、在终端运行以下命令

defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES
构建系统, 设置的地方没有找到

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

Action Points

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

架构影响 Architecture

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

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

Action Points

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

编译模式 Compilation Mode

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

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

优化级别 Optimization Level

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

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

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

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

依赖管理

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 BuildFind 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 LibrariesTarget Dependencies 。我们不必对 UIKitFoundation 这些隐式依赖项执行此步骤。

因此,这里的总体目标是对于外部 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) 注:xcode11默认就是这样。

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

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测试用例时,会消耗系统内核/资源。

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