Question or problem in the Swift programming language:
After changing single line of code and performing Xcode 10.1 incremental build in large project, Xcode spends most of build time in “Compile swift source files” phase after completing all listed tasks (compile changed files and merge swiftmodule are both completed)
screenshot showing tasks list: https://imgur.com/a/JoVI0zB
While compilation and merging swift module takes less than second, whole phase can take up to 2min in my project (300k LOC).
What does Xcode do in this time? Is there any way to speed up this process?
Similar project written in Obj-C takes just few seconds to launch after changing 1 line of code.
How to solve the problem:
I have this same issue and after a lot of investigation I have determined that whatever it is doing, it is based on the number of swift source files you have.
The Root Cause
In the 2018 wwdc talk about the Xcode build system the presenter says (searchable in the transcript of the video):
What this means is that unlike Clang, when compiling one Swift file, the compiler will parse all the other Swift files in the target.
So even if/though the incremental build only needs to recompile a single file, it still parses every other swift file.
Our project has over 2000 swift source files and we are seeing incremental build times of 3+ minutes for recompiling a single independent file.
Testing to prove the root cause
Because hearing it in a WWDC talk is not enough, I did the following to test it myself:
- Created a new project
- Tested incremental build times for nearly empty project, changing a single file
- Incremental build completes nearly instantaneously (< 1-2 seconds)
- Used a script to generate 2,000 swift files, each containing a simple struct with a unique name, 2 variables, and a simple function. There are no dependencies between any of the files.
- Add the directory containing the 2,000 new swift files to the project and complete a full build.
- Test incremental build times again for a single file
- Incremental build time took more than 1 minute
In my testing, the complexity of the contents of the additional swift files does not seem to be significant, only the number of files.
The Solution – Modularization/Frameworks
Separate your app in to smaller frameworks to reduce the number of swift source files in each target.
This is a decent guide showing steps on how to go about this if you don’t already know how.
In my test case above, I created a new framework and moved the 2,000 swift files into the framework and then tested incremental build times in the original project (importing the framework) and the build times were back to < 1-2 seconds. Of course doing an incremental build inside of the framework would still be slow, but ideally you would split the project into many and smaller frameworks so that each will have faster incremental build times.
Not the Solution – Merging Files
If the problem is the number of files, why not merge a bunch of files to reduce the number? Because it will likely result in slower incremental builds.
According to 2018 WWDC’s Building Faster in Xcode:
Swift’s dependency model is based around files
This relates to our current problem because it means that any change to a file (except for changes to function body contents only) will cause recompilation for any file that depends on ANYTHING in the changed file.
If you merge many types into a single file, it will result in more code getting recompiled on incremental builds if any of the changes touch the large file or any of its dependencies. It will also increase the number dependents the large file has, making it so all of them get recompiled if ANY of the types in the large file are modified.