Speeding up Xcode Builds
How Zalando Mobile achieved 80% faster Xcode builds and 30% faster compilation speeds.
In this post, I’d like to drill down a bit deeper on one topic of great importance to us: development speed.
My interest in this subject was recently piqued after I read Spotify’s blog post about how their mobile team sped up their Xcode build time in the Edit-Build cycle. I started to wonder if Zalando might achieve similar results. So I experimented a bit— changing our Xcode build settings and playing around with different Swift coding styles. The result: 80% faster build times, which in turn saved time for every Zalando iOS dev.
How I Did It
Thanks to some insight from StackOverflow, the first thing I did was to enable build duration setting in Xcode. This produced new measurements that I could easily compare with the old:
$ defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES
The Zalando mobile team uses Cocoapods, so we can add a post-installation phase to a Podfile to change the Debug Information Format setting in all targets:
post_install do |installer|
puts("Update debug pod settings to speed up build time")
Dir.glob(File.join("Pods", "**", "Pods*{debug,Private}.xcconfig")).each do |file|
File.open(file, 'a') { |f| f.puts "\nDEBUG_INFORMATION_FORMAT = dwarf" }
end
end
After making this change in a separate branch, I compared before and after build times for:
- changes in Swift source code (add a println() statement)
- changes in Objective-C source code (add NSLog(@“”) statement)
- builds after cleaning the workspace
The results were surprising: Turning on “dwarf” setting improved our Edit-Build time by 70-80%, and reduced our clean build time by 15% (22 seconds). Not bad.
A Second Experiment
I wanted more speed. After several Edit-Build cycles, I noticed that compiling Swift files takes an eternity to complete. As it turns out, it is slower to compile changes in Swift source code than to simply make changes in Objective-C source code. More specifically, making changes to Swift files triggers recompilation of most of Obj-C files because we are importing a Xcode-generated Swift header file in many places across the app. Looks like Objective-C gives the compiler better hints than Swift does.
After identifying the files slowest to compile, and conducting some additional experiments, I found that using extensions too generously -- for example, having a class extension for each protocol implementation -- increases build time. So I converted most of the extensions in Swift files to simple class methods, which resulted in a four-second median improvement (seven-second average) in compilation time. The change was simple -- I just merged all class extensions into one class:
// Before
class SizeViewController: UIViewController {
…
}
extension SizeViewController: UITableViewDataSource {
…
}
extension SizeViewController: UITableViewDelegate {
…
}
extension SizeViewController: SizeCellDelegate {
…
}
…
// After
class SizeViewController: UIViewController,
UITableViewDataSource, UITableViewDelegate,
SizeCellDelegate {
…
}
Of course, there are always trade-offs. Although you’ll get faster build time by merging all extensions into one class, at the same time you sacrifice code readability and ease of maintenance. For the sake of experiment, I’ve decided to continue with one big class.
I’ve written a small benchmark that compares the compilation time of a class with N method and the compilation time of a class with N single-method extensions. You can find the benchmark code on GitHub:
The compilation time of extensions increases after 3,000 methods. Although the chart suggests that even 3,000 extensions compile as quickly as a single class with 3,000 methods, it usually doesn’t work this way in a real project. This is because of the method’s structure: for example, using instance variables will increase your compilation time. In the end, I hope our compilation times will improve with newer versions of Xcode and Swift.
We're hiring! Do you like working in an ever evolving organization such as Zalando? Consider joining our teams as a Mobile Engineer!