我使用的是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悄悄地引入了一个新的构建系统来改善当前糟糕的性能。您必须通过工作空间设置启用它。

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


当前回答

事实证明罗伯·纳皮尔是对的。这是一个文件(实际上是一个方法),导致编译器去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编译器(至少到今天为止)还没有打开,我猜你的问题没有真正的答案。

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

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

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

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

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

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

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

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

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

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

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毫秒的每个函数发出警告。

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

在我的情况下,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代码编译得更快。