WWDC18: Modern Tips for Optimising Swift Build Time in Xcode 10
With Xcode 10, there are some big enhancements in the performance and developer productivity while building Swift projects. The build times are boring and might distract developers if builds are taking longer, Apple has put some efforts to optimize the build times especially for the Swift language. Basically, build time differ depending on lines of code, the number of lines of code, dependency and machine configuration that has been used to build. There are various new features announced this year both in Xcode 10 and Swift compiler to make out Swift builds quicker than ever. There are various tips and tricks already covered in this GitHub repository but we will cover whats new in Xcode 10 and Swift 4.2 that will help us to make our Swift builds much faster.
1. Understand Xcode Build Process
The build process is a collection of tasks that have been performed under the hood using some native command line tools like swiftc, clang, ld, actool, ibtool etc. It involves Compiling and Linking the Source Code, Copying Resources like Assets, StoryBoards, Code Signing and Custom things with build scripts and many more. However, Xcode build process automates these tasks effectively and in the right order so that we can speed up the build process. The compilation of Swift source files can be done in parallel if possible so that linker can take all these tasks at once to prepare the executable of the apps. Xcode build system takes cares of everything from analyzing dependencies and determine which tasks can be run in the parallel. It’s also important to understand the concept of the incremental build so that we don’t need to build all the things if we are not changing it. You will know more about how Xcode build process work behind the scene by watching this WWDC session. In order to speed up the build process, we should help build system to do its job faster by identifying and optimizing dependencies in our apps. That’s why it’s important to understand how the Xcode build process works under the hood.
2. Use New Build System
Apple launched new build system in Xcode 9 but it was not activated by default. However, with Xcode 10, the new build setting has been activated by default and enabled from Xcode Files-> Project/Workspace Settings
Check the previous blog post on Xcode new build system for detailed information about the build system. If you are building an iOS project from the command line using the xcodebuild then we have to pass additional parameter -UseNewBuildSystem=YES will also force the new build system. The new build system is called xcbuild . The binary of the Apple’s xcbuild is located at the path
3. Parallelize Swift Builds
Xcode projects composed of multiple targets, one target may be dependent on another target to build. Xcode target specifies a product to build e.g we might have a different target for main app and unit or UI tests. When one target is depending on another target to build then it creates dependency e.g to unit tests for the iOS app, we first need to build and the main app and then we can build unit test target to execute unit tests. In Xcode Build Phases, we can specify the target dependencies explicitly using the Target Dependencies and implicitly using Link Binary with Libraries
Building the targets serially will take time and may not be good for utilizing system resources. It would be awesome to build the targets if possible. With Xcode 10, we can parallelize the build and analyze the dependencies under the hood which increase the build time significantly. We can enable parallel build by editing the Xcode Scheme and checking the parallel builds in the build action of the scheme.
Make sure, you checked both Parallelize Build and Find Implicit Dependencies to make sure we get the best performance out of Xcode. The parallelize build will make sure that builds are running in parallel if possible and Find Implicit dependencies will check all the dependencies inside our project, usually at “Link Binary with Library” build phase. Xcode 10 also introduced the feature of “Parallelized Target Build Process” which mean dependent target can begin compilation as soon as possible without waiting first to finish, however it must wait for the “Run Script” phase of the first target.
4. Improve Run Scripts Phase
In Xcode, build phases, we can add custom run scripts to make our build process as per our own project needs. The good example would be Carthage, if you have used Carthage for dependencies management you must have experienced the run scripts phase. We have to add Run Script phase and input and output files as shown below
As you can see that, we have specified Input Files to the run scripts phase which is important for the build system to make the decision if run scripts need to be run or not for the dependent target build. It’s always the good idea to provide the input files to the run scripts wherever applicable. When the number of input files grown up then Xcode 10, gives us the way to specify all the input files in the .xcfilelist format and we can add this file as File List input in the build phase. Xcode build system will always run this build phase when there are no input files, changed input files or missing output file. It’s very important to add those files to avoid running this phase for all the incremental builds when it’s not required. There is also documentation in Xcode help for Run Script phase
You can read more information about the “Run Script” phase in the documentation.
5. Analyse Build Time
With Xcode 10, we can analyze the build process with timing summary which helps us to determine where our build is taking the time and take corrective action to reduce the build time. This new feature will show the timings of each task performed by the build system and can be found in the Xcode Product –> Perform Action –> Build with Timing Summary option.
This feature can also be enabled using command line using xcodebuild tool.
$ xcodebuild -showBuildTimingSummary
This will print the build summary timing in the logs. By using this technique we can analyze that what needs to be done in order to make build faster.
6. Compilation Mode Vs Optimization Level
Recently Apple separated build settings “Compilation Mode” and “Optimisation Level” so that we can control compilation mechanism over the debug builds. The compilation mode is a mode of how our project builds where we can set whether we need to optimize for speed or skip it for the building in the debug mode. In case of the debug builds, the whole optimization is not always required so that we can set this option as incremental for the debug builds. In Xcode 10, by default Compilation Mode is set to “Incremental”. This will speed up the build drastically as the Whole Module stops doing the incremental builds and builds all the file during the build process. The “Optimization Level” build setting can be set to “No optimization” for the debug builds and for the release builds it can be set to “Optimize for Speed”. You can see that there is a new option introduced in the Xcode 10 that is “Optimize for Size” which can be used for the check the code size. This is machine code generated by compiling the app not the size of app or assets.
You can see that Xcode 10 has enabled the right options by default. If you are not using those options then you should change those options immediately to get benefit from the new Xcode features.
7. Manage Dependencies Smartly
Every app has either internal or external dependencies that might slow down the build process. Its good idea to do an inspection of dependencies and sort them out correctly to help Xcode build system to perform better. The dependencies come from various places in the Xcode project, they might be built-in, target dependencies or implicit dependencies in Link Binary with Libraries, Build phase dependencies or scheme order dependencies. We have to manage them well for the better speed which involves
- Choosing right dependency management solution. Carthage Or CocoaPods, check out the previous post to choose between Carthage or CocoaPods
- Add implicit dependencies rather than explicit dependencies
- Manage projects and workspaces so that all the codes accessible easily
- Determine the strategy to put the dependency source code in the repository or download it dynamically.
- Use caching strategy of dependency management to speed up the build
With the combination of Xcode 10 and Swift 4.2, we can take advantage of new features launched by Apple this year to speed up the Swift builds. Hope you like these tips collected from WWDC2018 and implement in your current project. Let me know what you think of these features of Xcode 10 in the comments below.