Optimizing Build Times in Swift 4

Tyler Milner
Atlas
Published in
19 min readNov 29, 2017

As Swift has matured, so has its compiler. We’ve come a long way since the days of the notoriously slow build times of Swift 2. Combined with the general bugginess of Xcode at the time, I’m not sure how we ever got work done back then! Although compile times seem to have gotten much better with Swift 4 and Xcode 9, are there any tweaks that we can apply to further increase performance? What about our Swift code? If you’ve followed the issue at all over the past two years, you may have run into recommendations for how to optimize your Swift code to speed up compilation times. Are those tweaks still necessary as well? In this post, I’ll do my best to answer these questions as well as provide some learnings from my own experiences with build times in Swift 4.

Swift Build Times — Past, Present, and Future

Though slow build times have been observed since the early days of Swift, I feel like it didn’t really become a mainstream issue until Matt Nedrich posted his observations about a seemingly simple code snippet that took 12+ hours to compile. Of course, the bug was fixed in Swift 3, along with improvements to compile times in projects that mixed Swift and Objective-C code. Swift 4 has continued to improve compile times, and I have no doubt that we’ll see even more improvements when Swift 5 is released next year.

Improving Swift Build Times

When to Start Doing Something

Sometimes it’s obvious that something isn’t quite right with your compile times. You may notice that your project seems to be taking forever to compile — especially if you’re switching branches often. There can also be other situations in which you may want to spend some time optimizing your build times:

  • Midway through the project — find potential issues early as part of a mid-project “health check”
  • Towards the end of the project — ensure a quality initial release and set yourself up for success on future releases
  • Inheriting a codebase — fixing slowdowns now should pay dividends in the long run

Improving Swift Build Times

What Can You Do?

There are two main things that you can do to improve your build times — adjust your project settings or adjust your Swift code. Adjusting your project settings is an easy win that can yield significant improvements and is definitely worth the time investment. Adjusting your Swift code takes more time, but can also yield positive results.

Adjusting Project Settings

Build Active Architecture Only

Make sure Build Active Architecture Only is set to Yes for Debug builds. This is the default when creating a new Xcode project, but it’s good to double-check to make sure it didn’t accidentally get set to something else.

Set Build Active Architecture Only to Yes for Debug builds.

Debug Information Format

Make sure Build OptionsDebug Information Format is set to DWARF for Debug builds. This is the Xcode default, but is good to double-check.

Set Debug Information Format to DWARF for Debug builds.

NOTE: If you’re using Fabric/Crashlytics, you may need to keep this set to DWARF with dSYM File. I started seeing console warnings from Fabric when I tried to apply this change, so I had to change it back.

Enable Whole Module Optimization with Build Flag

T̵h̵i̵s̵ ̵t̵w̵e̵a̵k̵ ̵i̵n̵v̵o̵l̵v̵e̵s̵ ̵e̵n̵a̵b̵l̵i̵n̵g̵ ̵W̵h̵o̵l̵e̵ ̵M̵o̵d̵u̵l̵e̵ ̵O̵p̵t̵i̵m̵i̵z̵a̵t̵i̵o̵n̵ ̵(̵W̵M̵O̵)̵ ̵f̵o̵r̵ ̵D̵e̵b̵u̵g̵ ̵b̵u̵i̵l̵d̵s̵ ̵b̵u̵t̵ ̵t̵u̵r̵n̵i̵n̵g̵ ̵t̵h̵e̵ ̵a̵c̵t̵u̵a̵l̵ ̵o̵p̵t̵i̵m̵i̵z̵a̵t̵i̵o̵n̵ ̵p̵a̵r̵t̵ ̵o̵f̵f̵ ̵v̵i̵a̵ ̵a̵ ̵b̵u̵i̵l̵d̵ ̵f̵l̵a̵g̵.̵ ̵T̵h̵i̵s̵ ̵a̵l̵l̵o̵w̵s̵ ̵y̵o̵u̵ ̵t̵o̵ ̵s̵t̵i̵l̵l̵ ̵p̵r̵o̵p̵e̵r̵l̵y̵ ̵d̵e̵b̵u̵g̵ ̵y̵o̵u̵r̵ ̵c̵o̵d̵e̵ ̵a̵t̵ ̵r̵u̵n̵t̵i̵m̵e̵.̵

U̵n̵d̵e̵r̵ ̵S̵w̵i̵f̵t̵ ̵C̵o̵m̵p̵i̵l̵e̵r̵ ̵-̵ ̵C̵o̵d̵e̵ ̵G̵e̵n̵e̵r̵a̵t̵i̵o̵n̵,̵ ̵s̵e̵t̵ ̵t̵h̵e̵ ̵O̵p̵t̵i̵m̵i̵z̵a̵t̵i̵o̵n̵ ̵L̵e̵v̵e̵l̵ ̵f̵o̵r̵ ̵D̵e̵b̵u̵g̵ ̵t̵o̵ ̵F̵a̵s̵t̵,̵ ̵W̵h̵o̵l̵e̵ ̵M̵o̵d̵u̵l̵e̵ ̵O̵p̵t̵i̵m̵i̵z̵a̵t̵i̵o̵n̵.̵ ̵O̵n̵c̵e̵ ̵y̵o̵u̵ ̵d̵o̵ ̵t̵h̵i̵s̵,̵ ̵a̵l̵l̵ ̵o̵f̵ ̵y̵o̵u̵r̵ ̵c̵o̵n̵f̵i̵g̵u̵r̵a̵t̵i̵o̵n̵s̵ ̵w̵i̵l̵l̵ ̵l̵i̵k̵e̵l̵y̵ ̵e̵n̵d̵ ̵u̵p̵ ̵b̵e̵i̵n̵g̵ ̵s̵e̵t̵ ̵t̵o̵ ̵t̵h̵i̵s̵ ̵v̵a̵l̵u̵e̵.̵

Set Optimization Level to Fast, Whole Module Optimization for Debug builds.

T̵h̵e̵n̵,̵ ̵u̵n̵d̵e̵r̵ ̵S̵w̵i̵f̵t̵ ̵C̵o̵m̵p̵i̵l̵e̵r̵ ̵-̵ ̵C̵u̵s̵t̵o̵m̵ ̵F̵l̵a̵g̵s̵ ̵→̵ ̵O̵t̵h̵e̵r̵ ̵S̵w̵i̵f̵t̵ ̵F̵l̵a̵g̵s̵,̵ ̵a̵d̵d̵ ̵t̵h̵e̵ ̵”̵-̵O̵n̵o̵n̵e̵”̵ ̵f̵l̵a̵g̵ ̵f̵o̵r̵ ̵t̵h̵e̵ ̵D̵e̵b̵u̵g̵ ̵c̵o̵n̵f̵i̵g̵u̵r̵a̵t̵i̵o̵n̵.̵

Add the -Onone flag to Other Swift Flags for Debug builds.

T̵h̵i̵s̵ ̵a̵l̵l̵o̵w̵s̵ ̵f̵o̵r̵ ̵t̵h̵e̵ ̵w̵h̵o̵l̵e̵ ̵m̵o̵d̵u̵l̵e̵ ̵t̵o̵ ̵b̵e̵ ̵c̵o̵m̵p̵i̵l̵e̵d̵ ̵a̵t̵ ̵o̵n̵c̵e̵ ̵w̵h̵i̵l̵e̵ ̵a̵l̵s̵o̵ ̵n̵o̵t̵ ̵p̵e̵r̵f̵o̵r̵m̵i̵n̵g̵ ̵a̵n̵y̵ ̵o̵p̵t̵i̵m̵i̵z̵a̵t̵i̵o̵n̵ ̵o̵f̵ ̵t̵h̵e̵ ̵c̵o̵d̵e̵.̵ ̵O̵n̵ ̵m̵a̵n̵y̵ ̵p̵r̵o̵j̵e̵c̵t̵s̵,̵ ̵I̵’̵v̵e̵ ̵s̵e̵e̵n̵ ̵t̵h̵i̵s̵ ̵t̵w̵e̵a̵k̵ ̵s̵p̵e̵e̵d̵ ̵u̵p̵ ̵c̵o̵m̵p̵i̵l̵e̵ ̵t̵i̵m̵e̵s̵ ̵b̵y̵ ̵a̵s̵ ̵m̵u̵c̵h̵ ̵a̵s̵ ̵2̵x̵!̵

Update — July 2019

At WWDC 2018, Apple highlighted this tweak as something that should no longer be necessary (both in the What’s New in Swift session and Building Faster with Xcode). They’ve reduced the amount of redundant work being done by the compiler (something that enabling Whole Module Optimization did as a side effect). By leaving the Compilation Mode set to Incremental and Optimization Level set to No Optimization for Debug builds, you should have as fast, or faster builds as was possible when using this tweak. This is especially true for incremental Debug builds, since the Whole Module Optimization setting no longer causes the entire target to be rebuilt when you make small changes to files.

Optimal Swift compilation settings for Xcode 10 / Swift 4.2 and later.

New Xcode Build System

Xcode 9 introduced a new build system “preview” that you can opt-in to. Dan Zinngrabe did some testing and found it to be 28% faster for clean builds and 82% faster for incremental builds. I personally saw a 6 second speedup in one of my projects, which was a 19% improvement for clean builds. You may also notice more responsiveness in the Xcode source editor, which is icing on the cake.

To enable the new build system, go to FileProject Settings (or Workspace Settings).

Navigate to FileProject Settings. If you’re using Cocoapods, it will be called Workspace Settings instead.

Change Build System to New Build System (Preview).

Change the Build System to New Build System (Preview).

Update — July 2019

As of Xcode 10, the New Build System is now the default. Some projects might still require the Legacy Build System, but you should work to get your project on the New Build System as soon as possible.

The New Build System is now the default in Xcode 10 and later.

Xcode 9.2 Parallel Commands Opt-In Feature

With the release of Xcode 9.2, Apple shipped an experimental opt-in feature that increases the number of concurrent build tasks that are run for Swift projects. Apple states that this may reduce build times at the expense of memory usage.

To opt-in, run the following command in Terminal:

defaults write com.apple.dt.Xcode BuildSystemScheduleInherentlyParallelCommandsExclusively -bool NO

To opt-out, delete the preference:

defaults delete com.apple.dt.Xcode BuildSystemScheduleInherentlyParallelCommandsExclusively

It’s worth noting that in my tests, I saw no difference in build times after enabling the feature, but your milage may vary.

Reduce Dependencies

The results from optimizations here will vary from project to project, but as a general rule of thumb, it’s good to keep project dependencies to a minimum when possible. The idea is that less code to compile == faster compile times.

New Swift features can also remove the need for some common libraries. Swift 4 saw the release of Codable, which removes the need for third party JSON parsing/serialization libraries. Next year, Swift 5 may introduce a native concurrency mechanism which could remove the need for third party promise/future/operation asynchronous libraries.

Causes of Slow Build Times

Per Robert Gummesson, there are two main causes of slow build times in Swift:

  • An individual routine takes too long to compile — to speed things up, you can try to rewrite your code in such a way that it compiles faster
  • Closures and lazy properties are getting type-checked too many times, which seems to be much less of an issue in Swift 4 — I’ve not seen any code get type-checked more than once in my testing

Adjusting Your Swift Code

How to Know What to Adjust

The easiest way to identify the “problem code” in your project that may be taking a long time to compile is to use the Swift BuildTimeAnalyzer tool. Follow the setup instructions to allow the code in your project to be profiled by the tool, and then perform a clean build. Once the build is finished, you can see exactly how long each function took to compile.

Example output from Swift BuildTimeAnalyzer — photo credit to Robert Gummesson.

Common Swift “Problem Code”

Over the past few years, many people have made public their observations about what kind of Swift code seems to give the compiler trouble. Fortunately, much of this doesn’t seem to apply anymore in Swift 4 (from my own observations, at least). If you’re curious about what has caused slowdowns in the past, feel free to check out my Gist or the “Resources” section at the end of the article.

My “Problem Code” Observations in Swift 4

While much of the “problem code” that seems to have plagued Swift in the past seems to no longer be an issue, I did observe some common themes that seemed to cause some minor slowdowns in Swift 4.

Guard and FatalError

This involved an extra guard statement that wasn’t providing much value. Before optimization, this method was taking 182ms to compile:

func item(forIndexPath indexPath: IndexPath) -> SomeCellModel {    let itemIndex = indexPath.row + (page * pageSize)    guard itemIndex < cellModels.count else {
fatalError("Index out of bounds")
}
return cellModels[itemIndex]}

Since the system will produce a fatalError when accessing an array index out of bounds, there was no need to have the extra guard leading to a manually defined fatalError. After removing the extraneous guard, the method took 91ms to compile:

func item(forIndexPath indexPath: IndexPath) -> SomeCellModel {    let itemIndex = indexPath.row + (page * pageSize)    return cellModels[itemIndex]}

Complex Methods

I saw several instances where methods that were doing several things were taking a long time to compile. One common theme was doing a lot of setup and configuration directly in viewDidLoad(). In the following example, viewDidLoad() was taking 135ms to compile:

// MARK: - Lifecycleoverride func viewDidLoad() {    super.viewDidLoad()    guard let detailState = detailState else { fatalError("\(DetailState.self) not provided to \(self)") }    title = detailState.navTitle    navigationController?.navigationBar.titleTextAttributes = [.font: UIFont.systemFont(ofSize: 20)]    navigationController?.navigationBar.tintColor = .lightGray    validationController = SomeValidationController(fieldProvider: self, detailState: detailState)    validationView.delegate = self    setupRadioButtonView()}// MARK: - Privateprivate func setupRadioButtonView() {    guard let detailState = detailState else { return }    radioButtonView.configure(withButtonConfig: detailState.radioButtonConfig)    radioButtonView.delegate = self}

It’s worth noting as well that the setupRadioButtonView() method above did not show up in the BuildTimeAnalyzer, which means that it took less than 10ms to compile.

After performing some cleanup of viewDidLoad() to better adhere to the single responsibility principle, the method took 106ms to compile.

// MARK: - Lifecycleoverride func viewDidLoad() {    super.viewDidLoad()    guard let detailState = detailState else { fatalError("\(DetailState.self) not provided to \(self)") }    setupNavigationBar(withState: detailState)    setupValidationController(withState: detailState)    setupRadioButtonView(withState: detailState)}// MARK: - Privateprivate func setupNavigationBar(withState detailState: DetailState) {    title = detailState.navTitle    navigationController?.navigationBar.titleTextAttributes = [.font: UIFont.systemFont(ofSize: 20)]    navigationController?.navigationBar.tintColor = .lightGray}private func setupValidationController(withState detailState: DetailState) {    validationController = SomeValidationController(fieldProvider: self, detailState: detailState)    validationView.delegate = self}private func setupRadioButtonView(withState detailState: DetailState) {    radioButtonView.configure(withButtonConfig: detailState.radioButtonConfig)    radioButtonView.delegate = self}

It’s worth noting the compile times of the two private methods that were created as well. The setupNavigationBar(_:) method took 29ms to compile and the setupValidationController(_:) method did not register on the BuildTimeAnalyzer, so it also took less than 10ms to compile. So, did we end up saving any time by splitting up viewDidLoad() into these methods? If you add up the times for all three methods, it looks like this:

106ms + 29ms + <10ms = 136-144ms

Compared to the original compile time of 135ms for viewDidLoad(), you may think that we’re actually slowing things down. However, it’s important to remember that the compilation time for a function often varies between 10–20ms between runs of the BuildTimeAnalyzer. While it’s possible that everything is compiling more slowly, the reality is that the compilation times are at least as fast, if not faster after splitting up the method than they were before. You also get the added benefit of the code being more readable and better adhering to separation of concerns.

String Interpolation

One thing I found interesting when analyzing my code using the BuildTimeAnalyzer was that it appears that String concatenation is no longer an area that the Swift 4 compiler struggles with. When I first encountered the code snippet below, I thought that I had hit a wall since I didn’t think there was a way to make the code compile faster:

struct StorageConfig {    let storageName: String    let installationId: String    var storageIdentifier: String {        return "\(storageName)_\(installationId)"    }}

The storageIdentifier property above was taking 127ms to compile. Just for kicks, I decided to replace the String interpolation with String concatenation. This allowed the property to compile in 60ms:

struct StorageConfig {    let storageName: String    let installationId: String    var storageIdentifier: String {        return storageName + "_" + installationId    }}

I’m not sure if my findings will hold true in all cases, but if you’re finding that you’d like to reduce the compilation time of some of your code that uses String interpolation, give String concatenation a try.

Manually Optimizing Swift Code — Lessons Learned

The Swift 4 compiler seems to be a huge step forward for reducing compilation speed bottlenecks. Here’s what I’ve learned:

  • It’s not necessary to sacrifice Swift’s syntactic sugar and concise coding styles for faster compilation times anymore
  • Play around with String interpolation vs. String concatenation — interpolation might not always be faster
  • The Swift 4 compiler seems to be much more efficient at only type-checking things once
  • High compile times can be an indicator of poor separation of concerns. Use this as a guide to break apart complex functions when possible

Should You Manually Optimize Your Swift Code?

I’ve shown you how, but should you put in the time to manually optimize/adjust your Swift code to speed up compile times? In general, I do not think the time saved by speeding up compilation times will be worth the time investment that’s needed to adjust or “fix” your code that’s compiling slowly. However, as mentioned above, taking the time to identify problem areas can be a good way to make improvements to how well your code adheres to the single responsibility principle and separation of concerns.

Additionally, Swift will continue to improve over time. This means that the benefits from current workarounds will diminish over time. Sacrificing conciseness and Swift syntactic sugar now will probably not be beneficial in the long run.

In my testing, I only saw a clean build speed up of about 2 seconds after fixing 23 instances of “problem code” that compiled slowly in one of my projects. In case you’re wondering, I considered anything over 130ms to be something that I needed to fix (though nothing was taking over 200ms to compile in the first place). This was for an unfinished project with about 10,000 lines of non-commenting, non-whitespace Swift 4 code.

Should You Care About Swift Compile Times?

If I don’t recommend optimizing your Swift code to improve compile times, should you even care about the compile times in your project? The short answer is YES, absolutely! Taking at least some steps to improve compile times in your project has many benefits:

  • Spend less time compiling and more time writing code
  • Faster compilation times make it easier to maintain cognitive focus and “flow”
  • Devs that come behind you (including yourself) will thank you for not letting things get out of hand

Putting It All Together

What steps should you take to optimize your build times in Swift 4? Here are the steps I would take to get your project building a little faster.

Establish a Baseline

Run the Swift BuildTimeAnalyzer to check the current status of your project. Make sure there are no obvious issues. Personally, I would consider any code that takes longer than 250–500ms to compile something that should be fixed before performing more optimizations. This will of course vary based on the size of your project and your computer’s processing horsepower. In one of my projects, I saw a file take more than 11 seconds to compile. The code in question involved the JSON decoding library Argo, which I planned to replace anyway since Swift 4’s Codable is a much nicer solution.

Manually Adjust Code (Optional)

I’ll leave it up to you to decide if you think it’s necessary to make slight adjustments to your code to reduce the amount of time it takes to compile. At the very least, you may want to use the data to improve how well your functions adhere to the single responsibility principle.

Adjust Your Project Settings

If nothing else, do this! It will only take you a few minutes to make sure your project settings are optimized and can often cut your clean build times in half.

Also, give the new build system in Xcode 9 a shot. While it’s still in “preview,” it may improve your build times as well as your overall Xcode source editor experience.

Reduce Dependencies

In general, it’s beneficial to keep your dependencies to a minimum so that you’re not reliant on others to fix/update code when bugs are found or new versions of Xcode/Swift are released. Improvements to Swift can also negate the need for third party libraries, like the advent of Codable to replace the need for third party JSON parsing libraries.

Future-proof Your Project

Make sure to keep an eye on compile times as your project matures. Rather than remembering to run the BuildTimeAnalyzer every so often, consider making some final project settings tweaks to generate warnings when code takes a long time to compile.

In your project’s build settings, under Swift Compiler — Custom FlagsOther Swift Flags, add the following two flags:

  • -Xfrontend -warn-long-function-bodies=200
  • -Xfrontend -warn-long-expression-type-checking=200
Future-proofing your Xcode project to output build warnings when functions take a long time to compile.

The first flag — warn-long-function-bodies — has been around for a while and will report any functions that take longer than the threshold to type-check. The second flag — warn-long-expression-type-checking — was recently introduced in Xcode 9 and will report any expressions that take longer than the threshold to type-check.

Feel free to tweak the warning threshold as you see fit for your project and build system. In the example above, anything that takes longer than 200ms will spit out a compiler warning when you build your project.

I̵f̵ ̵y̵o̵u̵ ̵e̵n̵a̵b̵l̵e̵ ̵t̵h̵e̵ ̵W̵h̵o̵l̵e̵ ̵M̵o̵d̵u̵l̵e̵ ̵O̵p̵t̵i̵m̵i̵z̵a̵t̵i̵o̵n̵ ̵t̵w̵e̵a̵k̵,̵ ̵y̵o̵u̵’̵l̵l̵ ̵l̵i̵k̵e̵l̵y̵ ̵w̵a̵n̵t̵ ̵t̵o̵ ̵l̵o̵w̵e̵r̵ ̵t̵h̵e̵ ̵v̵a̵l̵u̵e̵ ̵s̵i̵g̵n̵i̵f̵i̵c̵a̵n̵t̵l̵y̵ ̵a̵s̵ ̵w̵e̵l̵l̵ ̵s̵i̵n̵c̵e̵ ̵e̵v̵e̵r̵y̵t̵h̵i̵n̵g̵ ̵w̵i̵l̵l̵ ̵c̵o̵m̵p̵i̵l̵e̵ ̵m̵u̵c̵h̵ ̵f̵a̵s̵t̵e̵r̵.̵ ̵I̵’̵d̵ ̵r̵e̵c̵o̵m̵m̵e̵n̵d̵ ̵s̵t̵a̵r̵t̵i̵n̵g̵ ̵a̵t̵ ̵a̵ ̵1̵0̵0̵m̵s̵ ̵t̵h̵r̵e̵s̵h̵o̵l̵d̵ ̵a̵f̵t̵e̵r̵ ̵e̵n̵a̵b̵l̵i̵n̵g̵ ̵t̵h̵e̵ ̵W̵M̵O̵ ̵t̵w̵e̵a̵k̵.̵

Update — July 2019

As mentioned above, the Whole Module Optimization tweak should no longer be necessary and is no longer recommended.

An additional tweak you can apply to keep an eye on build times is to enable the build duration to be shown as part of the build status window in the center-top of Xcode by running the following command in Terminal:

defaults write com.apple.dt.Xcode ShowBuildOperationDuration -bool YES

Restart Xcode if it was already running and then perform a build. You’ll see the build duration is now shown next to the build status at the top of Xcode:

Enabling the build duration to be shown next to the build status.

Conclusion

I hope you found the information here useful. Overall, it seems that things are getting much better in terms of Swift compilation times, but there are still some small tweaks that can provide significant speedups in your project’s compilation times. I spent several hours researching this topic and gathering information and have listed my resources in the section below. Feel free to look them over and let me know if there’s anything I missed or if you have any of your own tips and tricks to speed things up. Personally, I was able to reduce build times from 61s to 26s (57% speedup) in one of my projects and 64s to 23s (64% speedup) in another project. At the end of it all, I hope you’ll take a few minutes to optimize your projects so that you can quit slacking off! 😉

The classic “my code’s compiling” programmer excuse via xkcd.

Resources

Update — July 2019

At WWDC 2018, Apple provided many good build speedup tips in the Building Faster in Xcode session. I’ve summarized some of the information below, but you should make sure to check out the video for all the details!

Parallelize Build

Make sure your scheme has the “Parallelize Build” option checked.

Enable the Parallelize Build option in your project’s scheme.

Examine and Optimize Dependencies

You should watch the WWDC video to get all the details, but essentially the main ideas are to make sure the dependency hierarchy is optimized for the targets in your app (e.g. if a target needs just a small section of a dependency, consider breaking it off into its own module). Also, make sure to routinely cleanup unused dependencies (much in the same way you want to periodically cleanup dead code).

Reduce the Amount of Rebuild Work for Run Script Phases

Populate the “Input Files” parameter for your run script with the files your script will read or use during the script execution. This will help Xcode determine if your run script phase should actually run or not. New in Xcode 10, you can also provide this information as an xcfilelist via the “Input File Lists” parameter. Likewise, populating the “Output Files” (or “Output File Lists”) parameter will also help Xcode determine if the run script phase should be executed.

Populate Input Files and Output Files parameters to help Xcode determine if the run script phase should be run or not.

Xcode uses the following logic to determine if your run script phase should be run:

  • No input files declared (i.e. Xcode will run the script for every build)
  • Input files changed
  • Output files are missing

This information is also now documented in the Run a shell script section of the Xcode Help.

Measuring Build Time

Xcode 10 now includes in-line task timings in the build log so that you can see how long each build task takes.

Xcode 10 build task timings in the build log.

You can also have Xcode generate a timing summary section at the end of the build log to get an aggregate timing of all of the tasks that went into the build operation.

Select ProductPerform ActionBuild with Timing Summary to have Xcode generate a Build Timing Summary section at the end of the build log.

Using the timing summary, you can check to make sure your run script phases are configured properly. If you see “PhaseScriptExecution” eating up time on every build (especially incremental builds), then it’s a good indicator that something isn’t configured properly.

Keep an eye on the amount of time being spent on PhaseScriptExecution during incremental builds.

Dealing with Complex Expressions

Although not as commonly necessary as in previous versions of Swift, sometimes explicitly providing type information can help speed up compilation.

Use Explicit Types for Complex Properties

For complex computed properties, explicitly providing type information can help keep the Swift compiler from having to spend extra time inferring the type. This also helps to improve readability, since the reader doesn’t need to spend time trying to figure out the type either.

Provide explicit type information for complex properties when necessary.

Provide Types in Complex Closures

Similar to the above, providing explicit type information for closures can help save the compiler some work.

Provide explicit type information for closures.

Break Apart Complex Expressions

Breaking up a complex expressions into separate statements can improve readability and improve compilation times.

Break up complex single-line statements into multiple lines.

Use AnyObject Methods and Properties Sparingly

Using the AnyObject type inherits some features of the id type from Objective-C. Because the type is not known, the Swift compiler must assume that any methods in the project marked as @objc can potentially be called. This means that the compiler must look across the entire project to make sure you’re calling a valid implementation. Instead of declaring conformance to AnyObject, it’s better to define and use a protocol for things like delegate types.

Define and use a protocol rather than AnyObject.

Understanding Dependencies in Swift

Understanding how Swift determines which files need to be recompiled can help you make sure you’re not introducing additional overhead during compilation time.

Incremental Builds are File-Based

For incremental builds, the Swift compiler determines what needs to be recompiled based on file changes. Adding/removing types from a file or changing a type’s interface will trigger a rebuild of that file and whatever files depend on it (including files in other targets). However, changing the implementation details of a function won’t cause dependent code to be recompiled. Although Apple didn’t explicitly state it, I would infer this to be a good reason why you should prefer splitting up types into several small files rather than declaring many types in a single file.

Limiting Your Obj-C/Swift Interface

As you probably know, the Obj-C bridging header defines the Obj-C code that’s exposed to Swift. Likewise, the Swift generated header defines the Swift code that’s exposed to Obj-C. The main idea here is to shrink the amount of content in these headers so that are fewer chances for things to change and decrease the need for files to be recompiled.

Use private when Possible

Using private in your Swift code will prevent methods from being exposed to Obj-C via the generated header. This includes @IBOutlet and @IBAction declarations, so making sure to declare these as private can help reduce the amount of code that winds up in the Swift generated header.

Make @IBOutlet properties and @IBAction methods private.

Another example is methods that need to be exposed to Obj-C for use with Obj-C runtime features, like NotificationCenter’s requirement to provide a #selector as a parameter when adding observers. Either marking the method referenced by the #selector as private or switching to block-based APIs can help ensure the method doesn’t make it into the Swift generated header.

Make methods used with NotificationCenter observers private.

Migrate to Swift 4

Migrating from Swift 3 to Swift 4 preserves @objc inference by default. This means that methods and properties on any subclass of NSObject are exposed to Obj-C. Make sure to finish your migration to Swift 4 by removing the override on the Swift 3 @objc Inference build setting (to set it to Default).

Set the Swift 3 @objc Inference build setting to Default.

Keep Your Bridging Header Minimal

For the Obj-C interface files (.h) declared in the bridging header, audit the properties and methods declared on each of those classes to make sure they’re actually used by Swift. It’s possible many of these can be moved inside of the implementation file (.m) to make them private and prevent them from being included as part of the Obj-C bridging header.

Make sure Obj-C header files included in the bridging header only contain methods and properties that need to be used in Swift.

--

--