Dhruvil Patel

7 min read

Sep 27, 2022

Press enter or click to view image in full size

As an iOS developer, we have encountered this problem frequently whereby, after starting the build, it takes a long time to get compiled and built which in turn tends to distract us from our focus zone and reduces productivity.

Courtesy @ios_memes

So we drilled down to identify and tackle the core cause.
After doing a bunch of optimizations, we reduced our build time by ~21%.

So let’s get started.

How did we analyse slow build time?

1. Measured total build time:

of the project by executing the below-mentioned command in the terminal:

defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES

post that after closing and reopening the Xcode again and building the project we’ll be able to see the total build time.

Press enter or click to view image in full size

Note:

Before measuring the build time, it’s recommended to clean the project(i.e ⌘+⌥+K) and delete project’s derived data as well. Below is the command to do the same:

rm -rf ~/Library/Developer/Xcode/DerivedData

2. Identified slow compiled code:

by adding below mentioned flags in "Other Swift Flags”section under Build settings:

-Xfrontend -warn-long-function-bodies=100
-Xfrontend -warn-long-expression-type-checking=100

Press enter or click to view image in full size

Slow compiled code snippet

Purpose of these flags:

  • warn-long-function-boides=100: Help’s to show the functions which took more than 100ms of time to compile
  • warn-long-expression-type-checking=100: Help’s to show the specific expression which took more than 100ms of time to compile.

Also to debug it further, we have added the below-mentioned flag under "Other Swift Flags”section as well:

-Xfrontend -debug-time-function-bodies

this will in turn show the list of compilation times of each function.

Report Navigator section

3. Enabled Build time summary:

This will help us provide a summary of time spent on each task, allowing us to pinpoint the exact task we need to focus in order to optimize our build time.

Press enter or click to view image in full size

Steps to trigger Build time summary

Press enter or click to view image in full size

Build time summary report

Note: Above shared summary is from an incremental build and we can observe that without doing any changes CodeSign took a good amount of time which somehow could have been avoided. Later in the article, we have mentioned ways to optimize that.

Clean build: To clean and rebuild whole project from scratch.
Incremental build: To rebuild a project after some code changes.

4. Third-party tool XCLogParser:

It is a log parser tool for Xcode-generated xcactivitylog and gives a lot of insights in regards to build/compilation times, warning, slowest targets etc. for every module and file in our project.

To generate the report, run the below-mentioned command in the terminal:

xclogparser parse --workspace <project-name>.xcworkspace --reporter html

Press enter or click to view image in full size

XCLogParser Report snapshot

and with the help of this report, we jotted top functions that were taking up a lot of compilation time.

Get Dhruvil Patel’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember me for faster sign in

Now that we have analyzed the reason behind the slow build time, let’s deep dive into ways to optimize it.

Code Optimizations guidelines:

1. Use “let” whenever possible:

  • It is recommended to use let when no modifications are required post the initialization i.e will behave as immutable variables.

2. Declare classes as final if possible:

  • If a class has no subclass and is not explicitly marked as final, the compiler has to look for subclasses in every other file which in turn adds overhead search time for the compiler.
  • Also if a class is defined as final it means it can’t be inherited so it uses the direct/static dispatch method instead of dynamic dispatch.

3. Explicit type declaration:

  • It’s best to specify the type to reduce the compiler’s work.
  • Also, avoid explicitly calling .init because the compiler needs to spend more time determining the type of init, so it’s preferable to declare the type instead.

4. Extension as private access modifiers:

  • It’s advisable to mark extensions as private if it’s not being accessed outside the class.
  • Pro tip: If we want to group all the private functions and avoid adding private to each of the functions, we can move all those functions under private extension. All code under this extension will now become private.

5. Avoid shorthand enums:

  • By specifically mentioning the type of case, it helped us to reduce a lot of compilation time.

6. Break complex expression into pieces:

  • Avoid long calculations in a single line & make it less complex
  • If a large amount of logic is handled in a single expression, the compiler’s effort is significantly increased.

7. Avoid unnecessary casting:

  • For example, the values were already Double and some parentheses were redundant.

8. Avoid nil judgment using the nil coalescing operator:

  • The compiler takes more time when solving the nil judgment using the ?? operator.
  • Rather than that, we should prefer if let condition check provided it won’t look complex in terms of user readability.

Xcode Build settings changes:

Though most of the below-mentioned build settings are by default set by Xcode but for older projects, some of these may not yet be set or may be overridden by other values so it’s always good to take a look and verify the same.

1. BUILD ACTIVE ARCHITECTURE ONLY

Debug: Yes
Release: No

2. COMPILATION MODE

Debug: Incremental
Release: Whole Module

3. OPTIMIZATION LEVEL

Debug: No Optimization [-Onone]
Release: Optimize for Speed [-O]

4. DEBUG INFORMATION FORMAT (DWARF)

Debug: DWARF
Release: DWARF with DSYM File

Code Signing Changes:

Code signing was consuming much time even if there were no code changes, so we debugged and resolved it for debug builds

  • Got incremental build reduced by ~50% by changing the hash algorithm that code signing uses only for debug builds
  • We replaced hashing algorithm with SHA-1, which is faster than the default SHA-256, by adding--digest-algorithm=sha1 to “Other Code Signing Flags” in the Xcode build settings
  • Also reduced the amount of stuff we have to hash in the first place by creating empty resourceRules.plist file

Resource rules are rules given in a plist, telling codesign what to sign and what not to

Press enter or click to view image in full size

Project target -> Build settings -> Other code Signing flags

resourceRules.plist file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>rules</key>
<dict>
<key>.*</key>
<false/>
</dict>
</dict>
</plist>

Before:

Press enter or click to view image in full size

Code signing time before optimization

After:

Press enter or click to view image in full size

Reduced code signing time after optimization