我遇到一个问题。我正在开发的一个全新的应用-是100%用Swift来写的。考虑到这个项目只有大概200个文件,我注意到它需要的编译时间超过了我的想象,更重要的是,它比前几个星期的编译速度慢了很多。在这个问题变得越来越糟糕之前,我需要尽快找到问题的根源。
第一步是添加 -Xfrontend -debug-time-function-bodies
这个Swift 编译选项:
这会让这个编译器输出一个函数编译的时长(感谢 Kevin Ballard 给我这个线索)。这个编译日志可以在Xcode 的 Report 窗口中看到,但需要你手动一个个展开每个文件:
下一步为了理解它们,需要把所有这些日志聚合在一个文件。
我们使用 xcodebuild
命令工具来把所有的日志输出到控制台:
# Clean and build, capturing only lines containing `X.Yms` where X > 0, sorting from largest to smallest
xcodebuild -workspace App.xcworkspace -scheme App clean build | grep [1-9].[0-9]ms | sort -nr > culprits.txt
接下来就是在这些输出中找到我想要的信息。我发现有超过1200行的数据是编译超过3秒的,谢天谢地的是这么多行都是三个相同的函数重复很多次(我对编译器不是很懂,不明白为什么会出现这种情况,但是输出的数据中包含了“closure”闭包这个关键词还是提供了一些线索)
3158.2ms /Users/Bryan/Projects/App/FileA.swift:23:14 @objc get {}
3157.8ms /Users/Bryan/Projects/App/FileA.swift:23:52 (closure)
3142.1ms /Users/Bryan/Projects/App/FileA.swift:23:14 @objc get {}
3141.6ms /Users/Bryan/Projects/App/FileA.swift:23:52 (closure)
3139.2ms /Users/Bryan/Projects/App/FileA.swift:23:14 @objc get {}
3138.7ms /Users/Bryan/Projects/App/FileA.swift:23:52 (closure)
3128.3ms /Users/Bryan/Projects/App/FileB.swift:27:22 final get {}
3109.9ms /Users/Bryan/Projects/App/FileA.swift:23:52 (closure)
3052.7ms /Users/Bryan/Projects/App/FileA.swift:23:14 @objc get {}
3052.6ms /Users/Bryan/Projects/App/FileA.swift:23:14 @objc get {}
3052.2ms /Users/Bryan/Projects/App/FileA.swift:23:52 (closure)
3052.1ms /Users/Bryan/Projects/App/FileA.swift:23:52 (closure)
3049.0ms /Users/Bryan/Projects/App/FileB.swift:27:22 final get {}
3026.1ms /Users/Bryan/Projects/App/FileB.swift:27:22 final get {}
更疯狂的是,所有这三个函数都是一行简单的代码。重写这三行代码就可以让我的项目编译时间快60%。我知道这很多人不爽,但是老实说,我非常高兴弄清楚了究竟是什么原因,如果下次遇到同样的问题,我知道自己该怎么做了。
你可能想知道这个三行代码是什么。这三行代码都非常相似,类似下面的:
return [CustomType()] + array.map(CustomType.init) + [CustomType()]
我不能确定造成这个问题是因为数组的 append 操作,或者mapping 操作,还是两者的结合。我放弃使用这些函数,不理会是否影响性能,添加了额外的临时变量和可变参数,用普通的方式改写。我不是第一个发现这个数组添加的操作很慢,就因为这样一行优雅,看似无害的代码让我亲身经历了这个痛苦。
Swift 仍然是一个非常年轻的语言,有这样的问题很正常,作为一个开放的社区,我们应该互相帮助,防止另一个痛苦发生。