我使用的是Xcode 6 Beta 6。

这个问题已经困扰我一段时间了,但现在已经到了几乎无法使用的地步。

我的项目开始有一个相当大的65个Swift文件和一些桥接Objective-C文件(这真的不是问题的原因)。

似乎对任何Swift文件的任何轻微修改(比如在应用程序中几乎不使用的类中添加一个简单的空白)都会导致指定目标的整个Swift文件被重新编译。

经过深入的调查,我发现几乎100%的编译器时间是CompileSwift阶段,Xcode在目标的所有Swift文件上运行swiftc命令。

我做了一些进一步的调查,如果我只保持应用程序委托与默认控制器的编译是非常快的,但随着我添加越来越多的项目文件,编译时间开始变得非常慢。

现在只有65个源文件,每次编译大约需要8/10秒。一点也不快。

除了这篇文章,我还没有看到任何关于这个问题的文章,但这是一个旧版本的Xcode 6。所以我在想是不是只有我一个人有这种情况。

更新

我在GitHub上检查了一些Swift项目,比如Alamofire、Euler和CryptoSwift,但没有一个项目有足够的Swift文件来进行真正的比较。我发现唯一一个开始有一个像样的大小的项目是SwiftHN,即使它只有12个源文件,我仍然能够验证同样的事情,一个简单的空间和整个项目需要重新编译,这开始需要一点时间(2/3秒)。

与分析和编译都非常快的Objective-C代码相比,Swift真的感觉永远无法处理大项目,但请告诉我我错了。

更新与Xcode 6 Beta 7

仍然没有任何改善。这开始变得荒谬了。由于Swift中缺少#import,我真的不知道苹果将如何优化这一点。

更新Xcode 6.3和Swift 1.2

苹果增加了增量构建(以及许多其他编译器优化)。你必须将你的代码迁移到Swift 1.2才能看到这些好处,但苹果在Xcode 6.3中添加了一个工具来帮助你做到这一点:

然而

不要像我一样高兴得太快。他们用于增量构建的图形求解器还没有很好地优化。

事实上,首先,它不会查看函数签名的变化,所以如果你在一个方法的块中添加一个空格,所有依赖于这个类的文件都将被重新编译。

其次,它似乎基于重新编译的文件创建树,即使更改不影响它们。例如,如果将这三个类移动到不同的文件中

class FileA: NSObject {
    var foo:String?
}
class FileB: NSObject {
    var bar:FileA?
}
class FileC: NSObject {
    var baz:FileB?
}

现在,如果您修改了FileA,编译器显然会将FileA标记为要重新编译。它还会重新编译FileB(基于对FileA的更改,这是可以的),但也会重新编译FileC,因为FileB是重新编译的,这非常糟糕,因为FileC在这里从不使用FileA。

所以我希望他们能改进依赖树求解器…我用这个示例代码打开了一个雷达。

更新与Xcode 7 beta 5和Swift 2.0

昨天苹果发布了beta 5,在发布说明中我们可以看到:

Swift语言和编译器 增量构建:仅改变函数体将不再导致依赖文件被重建。(15352929)

我已经试过了,我必须说它现在真的(真的!)很好。他们极大地优化了swift中的增量构建。

我强烈建议你创建一个swift2.0分支,并使用XCode 7 beta 5保持你的代码是最新的。你会对编译器的增强感到满意(但是我想说XCode 7的全局状态仍然很慢并且有bug)

用Xcode 8.2更新

距离我上次更新这个问题已经有一段时间了,所以就在这里。

我们的应用程序现在大约有2万行几乎全是Swift代码,这还不错,但并不突出。它经历了迅速的迁移。在2014年年中的Macbook pro (2.5 GHz英特尔酷睿i7)上编译大约需要5/6m,这在一个干净的构建上是可以接受的。

然而,增量构建仍然是一个笑话,尽管苹果声称:

Xcode不会在发生小变化时重新构建整个目标。(28892475)

显然,我认为我们中的许多人在检查完这些废话后只是笑了(添加一个私有(私有!)属性到我的项目的任何文件将重新编译整个东西…)

我想让你们看看苹果开发者论坛上的这个帖子,上面有关于这个问题的更多信息(也感谢苹果开发者偶尔就这个问题进行的交流)。

基本上,人们已经提出了一些改进增量构建的方法:

添加一个HEADER_MAP_USES_VFS项目设置为true 禁用从您的方案中查找隐式依赖项 创建一个新项目,并将文件层次结构移动到新项目。

我试试3,但是1/2行不通。

在整个情况中具有讽刺意味的是,看看关于这个问题的第一篇文章,当我们第一次编译时,我们使用的是Xcode 6和swift 1或swift 1.1代码,而现在大约两年后,尽管苹果进行了实际的改进,情况还是和Xcode 6一样糟糕。多么讽刺。

我真的很后悔在我们的项目中选择了Swift而不是Obj/C,因为它涉及到日常的挫折。(我甚至切换到AppCode,但这是另一个故事)

不管怎样,我看到这篇SO帖子有32k+的阅读量和143个上升量,所以我想我不是唯一一个。坚持住,伙计们,尽管对这种情况感到悲观,隧道尽头可能会有一线光明。

如果你有时间(和勇气!)我猜苹果欢迎雷达的关注。

更新Xcode 9

今天偶然发现的。Xcode悄悄地引入了一个新的构建系统来改善当前糟糕的性能。您必须通过工作空间设置启用它。

已经尝试过了,但会在完成后更新这篇文章。不过看起来很有希望。


因为所有这些东西都是测试版,而且Swift编译器(至少到今天为止)还没有打开,我猜你的问题没有真正的答案。

首先,比较Objective-C和Swift编译器有点残酷。Swift仍处于测试阶段,我相信苹果正在努力提供功能和修复错误,而不仅仅是提供闪电般的速度(你不会从购买家具开始盖房子)。我猜苹果会在适当的时候优化编译器。

如果出于某种原因,所有源文件都必须完整编译,那么可以选择创建独立的模块/库。但是这个选项还不可行,因为Swift在语言稳定之前不允许使用库。

我猜他们会优化编译器。由于同样的原因,我们不能创建预编译模块,很可能编译器需要从头编译所有内容。但是一旦语言达到稳定的版本,二进制文件的格式不再改变,我们将能够创建我们的库,也许(?)编译器也将能够优化它的工作。

不过,这只是猜测,因为只有苹果知道……


这可能与项目的规模无关。它可能是一段特定的代码,甚至可能只有一行。您可以通过尝试一次编译一个文件而不是整个项目来测试这一点。或者试着查看构建日志,看看哪个文件花了这么长时间。

这段38行的要点代码在beta7中要花一分钟多的时间来编译,这是可能导致麻烦的代码的一个例子。所有这些都是由这一个块引起的:

let pipeResult =
seq |> filter~~ { $0 % 2 == 0 }
  |> sorted~~ { $1 < $0 }
  |> map~~ { $0.description }
  |> joinedWithCommas

只需将其简化一两行,它几乎可以立即编译。问题是这会导致编译器中的指数增长(可能是阶乘增长)。显然,这并不理想,如果你可以隔离这种情况,你应该打开雷达来帮助清理这些问题。


另外,确保在为调试(Swift或Objective-C)编译时,你设置为仅构建活动架构:


事实证明罗伯·纳皮尔是对的。这是一个文件(实际上是一个方法),导致编译器去berzek。

别误会我的意思。Swift每次都会重新编译你所有的文件,但现在最棒的事情是,苹果在它编译的文件上添加了实时编译反馈,所以Xcode 6 GM现在可以实时显示哪些Swift文件正在编译以及编译状态,如下图所示:

因此,这是非常方便的,以了解您的文件花费这么长时间。在我的例子中,它是这段代码:

var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
        "url" : self.url?.absoluteString ?? "",
        "title" : self.title ?? ""
        ])

return dic.copy() as NSDictionary

因为属性标题类型是var title:String?而不是NSString。当把它添加到NSMutableDictionary时,编译器要疯了。

改为:

var dic = super.json().mutableCopy() as NSMutableDictionary
dic.addEntriesFromDictionary([
        "url" : self.url?.absoluteString ?? "",
        "title" : NSString(string: self.title ?? "")
        ])

return dic.copy() as NSDictionary

使编译从10/15秒(甚至更多)下降到一秒…很神奇的。


需要注意的一点是,Swift类型推断引擎在使用嵌套类型时可能非常缓慢。你可以通过观察各个编译单元的构建日志来大致了解导致缓慢的原因,这些编译单元花费了很长时间,然后将完整的xcode衍生命令复制并粘贴到终端窗口中,然后按CTRL-\以获得一些诊断。查看http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times以获得完整的示例。


解决办法就是铸造。

我有大量的字典,像这样:

["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
["title" : "someTitle", "textFile" : "someTextFile"],
.....

它花了大约40分钟来编译。直到我把字典这样排列:

["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
["title" : "someTitle", "textFile" : "someTextFile"] as [String : String],
....

这几乎适用于我遇到的关于硬编码到应用程序中的数据类型的所有其他问题。


在新的Xcode 6.3中,Swift编译时间得到了改进

Compiler improvements The Swift 1.2 compiler was engineered to be more stable and to improve performance in every way. These changes also provide a better experience when working with Swift in Xcode. Some of the most visible improvements include: Incremental builds Source files that haven’t changed will no longer be re-compiled by default, which will significantly improve build times for most common cases. Larger structural changes to your code may still require multiple files to be rebuilt. Faster executables Debug builds produce binaries that run considerably faster, and new optimizations deliver even better Release build performance. Better compiler diagnostics Clearer error and warning messages, along with new Fix-its, make it easier to write proper Swift 1.2 code. Stability improvements The most common compiler crashes have been fixed. You should also see fewer SourceKit warnings within the Xcode editor.


不幸的是,Swift编译器仍然没有针对快速和增量编译进行优化(截至Xcode 6.3 beta)。同时,你可以使用下面的一些技巧来提高Swift的编译时间:

Split the app into Frameworks to reduce recompilation impact. But be aware that you must avoid cyclic dependencies in your app. For futher info on this topic check this post: http://bits.citrusbyte.com/improving-swift-compile-time/ Use Swift for parts of your project that are quite stable and don't change often. For other areas where you need to change very often or areas that require lot of compile/run iterations to be complete (almost any UI related stuff), better use Objective-C with a mix-and-match approach. Try runtime code injection with 'Injection for Xcode' Use roopc method: http://roopc.net/posts/2014/speeding-up-swift-builds/ Relief the swift type inference engine by giving some hints with explicit casts.


Swift数组和字典构造似乎是一个相当流行的原因(特别是对于有Ruby背景的人来说),也就是说,

var a = ["a": "b",
         "c": "d",
         "e": "f",
         "g": "h",
         "i": "j",
         "k": "l",
         "m": "n",
         "o": "p",
         "q": "r",
         "s": "t",
         "u": "v",
         "x": "z"]

将可能是这应该修复它的原因:

var a = NSMutableDictionary()
a["a"] = "b"
a["c"] = "d"
... and so on

我在Xcode 6.3.1中没有任何工作-当我添加了大约100个Swift文件随机挂在构建和/或索引时。我尝试过模块化的选择,但没有成功。

安装和使用Xcode 6.4 Beta对我来说很有效。


下面是另一种可能导致类型推断大幅减速的情况。合并操作符。

像这样改变台词:

abs(some_optional_variable ?? 0)

to

abs((some_optional_variable ?? 0) as VARIABLE_TYPE)

帮助我把编译时间从70秒缩短到13秒


对于调试和测试,请确保使用以下设置将编译时间从20分钟左右缩短到2分钟以内,

在项目构建设置中,搜索“优化” 将调试调到“最快[-O3]”或以上。 为活动体系结构设置构建:是 调试信息格式:DWARF 全模块优化:NO

我浪费了无数的时间等待项目的构建,却意识到我必须做出一个小小的更改,并不得不等待整整30分钟来测试它。这些设置对我很有用。(我仍在尝试设置)

但是,确保你至少设置了“DWARF with dSYM”(如果你想监控你的应用程序),并将Build Active Architecture设置为“NO”,用于发布/存档以推送到iTunes Connect(我记得在这里也浪费了几个小时)。


重新启动我的Mac对这个问题有奇效。通过重新启动,我从15分钟构建到30秒构建。


如果您试图识别降低编译时间的特定文件,您可以尝试通过xctool从命令行编译它,它将逐文件提供编译时间。

需要注意的是,默认情况下,它会为每个CPU内核并发构建2个文件,并且不会提供“净”运行时间,而是绝对的“用户”时间。这样,并行文件之间的所有计时都很均匀,看起来非常相似。

为了克服这个问题,将-jobs标志设置为1,这样它就不会并行化文件构建。这将花费更长的时间,但最终您将拥有“净”编译时间,您可以逐个文件进行比较。

这是一个示例命令,应该可以做到这一点:

Xctool -workspace <your_workspace> -scheme <your_scheme> -jobs 1 build

“Compile Swift files”阶段的输出如下所示:

...
   ✓ Compile EntityObserver.swift (1623 ms)
   ✓ Compile Session.swift (1526 ms)
   ✓ Compile SearchComposer.swift (1556 ms)
...

从这个输出中,您可以快速确定哪些文件的编译时间比其他文件长。此外,您可以高精度地确定重构(显式强制转换、类型提示等)是否降低了特定文件的编译时间。

注意:从技术上讲,您也可以使用xcodebuild来实现,但输出非常冗长且难以使用。


也许我们无法修复Swift编译器,但我们可以修复的是我们的代码!

在Swift编译器中有一个隐藏的选项,它会打印出编译器编译每个函数所需的确切时间间隔:-Xfrontend -debug-time-function-bodies。它允许我们发现代码中的瓶颈,并显著改善编译时间。

在终端中简单运行如下命令并分析结果:

xcodebuild -workspace App.xcworkspace -scheme App clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep [1-9].[0-9]ms | sort -nr > culprits.txt

Awesome Brian Irace写了一篇关于它的精彩文章,分析你的Swift编译时间。


在我的情况下,Xcode 7没有任何不同。我有多个函数需要几秒钟来编译。

例子

// Build time: 5238.3ms
return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 22, height: bounds.height)

在打开可选项之后,构建时间下降了99.4%。

// Build time: 32.4ms
var padding: CGFloat = 22
if let rightView = rightView {
    padding += rightView.bounds.width
}

if let leftView = leftView {
    padding += leftView.bounds.width
}
return CGSizeMake(size.width + padding, bounds.height)

在这篇文章和这篇文章中可以看到更多的例子。

Xcode的构建时间分析器

我开发了一个Xcode插件,对于遇到这些问题的人来说可能会派上用场。

Swift 3似乎会有改进,所以希望我们能看到我们的Swift代码编译得更快。


对于Xcode 8,转到项目设置,然后编辑器>添加生成设置>添加自定义设置,并添加以下内容:

SWIFT_WHOLE_MODULE_OPTIMIZATION = YES

对于一个40KLOC的swift项目,添加这个标志使我们的清洁构建编译时间从7分钟下降到65秒,这是奇迹。也可以确认2个朋友在企业项目上也看到了类似的改进。

我只能假设这是Xcode 8.0的某种错误

编辑:在Xcode 8.3中,它似乎对某些人不起作用了。


我们已经尝试了很多方法来解决这个问题,因为我们有大约10万行Swift代码和30万行ObjC代码。

我们的第一步是根据函数编译时间输出优化所有函数(如https://thatthinginswift.com/debug-long-compile-times-swift/所述)

接下来我们写了一个脚本,将所有swift文件合并到一个文件中,这打破了访问级别,但它将我们的编译时间从5-6分钟缩短到1分钟。

现在这个功能已经失效了,因为我们询问了苹果公司,他们建议我们应该这样做:

在“Swift编译器-代码生成”构建设置中打开“整个模块优化”。选择“快速,整个模块优化”

在“Swift Compiler - Custom Flags”中,为您的开发构建添加“- onone”

设置这些标志后,编译器将一步编译所有Swift文件。我们发现合并脚本比单独编译文件要快得多。然而,如果没有'-Onone'覆盖,它也会优化整个模块,这是较慢的。当我们在其他Swift标志中设置'-Onone'标志时,它会停止优化,但它不会停止一步编译所有Swift文件。

关于整个模块优化的更多信息,请查看Apple的博客文章https://swift.org/blog/whole-module-optimizations/

我们发现这些设置允许我们的Swift代码在30秒内编译:-)我没有证据证明它如何在其他项目中工作,但我建议尝试一下,如果Swift编译时间对你来说仍然是一个问题。

注意,对于你的应用商店构建,你应该去掉“-Onone”标志,因为优化建议用于生产构建。


编译器花费大量时间推断和检查类型。因此,添加类型注释对编译器有很大帮助。

如果你有很多链式函数调用,比如

let sum = [1,2,3].map({String($0)}).flatMap({Float($0)}).reduce(0, combine: +)

然后,编译器需要一段时间来找出应该是什么类型的和。添加类型会有所帮助。还有一个有用的方法是将间歇步骤放入单独的变量中。

let numbers: [Int] = [1,2,3]
let strings: [String] = sum.map({String($0)})
let floats: [Float] = strings.flatMap({Float($0)})
let sum: Float = floats.reduce(0, combine: +)

特别是对于数字类型CGFloat, Int,它可以帮助很多。像2这样的文字数字可以表示许多不同的数字类型。所以编译器需要从上下文中找出它是哪一个。

也应该避免使用像+这样需要花费大量时间查找的函数。使用几个+来连接几个数组很慢,因为编译器需要找出每个+应该调用哪个+的实现。因此,如果可能的话,使用变量a: [Foo]和append()代替。

你可以添加一个警告来检测在Xcode中哪些函数编译缓慢。

在目标的构建设置中搜索其他Swift标志并添加

-Xfrontend -warn-long-function-bodies = 100

对编译时间超过100毫秒的每个函数发出警告。


对于混合Objective-C和Swift代码的项目,我们可以在Other Swift Flags中设置-enable-bridging-pch。这样,桥接头只被解析一次,结果(一个临时的“预编译头”或“PCH”文件)被缓存并在目标中的所有Swift文件中重用。苹果公司声称,它可以将构建时间缩短30%。参考链接:

注意:这只适用于Swift 3.1及以上版本。


这对我来说就像魔法一样-加速Swift编译。它将编译时间从10分钟减少到3分钟。

它说你应该打开整个模块优化,同时在其他Swift标志中添加-Onone。

我在Xcode 8.3/Xcode 8.2上使用Swift 3。


在一个表达式中混合使用整型字面值和浮点字面值也会导致很长的编译时间。

1.0 + (1.0 + (1  * (1.0 + 1.0))) // 3429ms

1.0 + (1.0 + (1.0  * (1.0 + 1.0))) // 5ms

许多1000+ms的编译时表达式在我在整型字面值后面加上.0后减少到10~100ms。