iOS Build Time Reduction

Written by Ashutosh Lasod
"If you lie to the compiler, it will get its revenge" - Henry Spencer
🤔 Ever wondered how many times a developer builds a project and how much developer time gets wasted on this? We consider build time as a break to grab a coffee or chat with someone or just start browsing the trending news. Though taking a break is good, getting distracted in the middle of a deep focus stretch creates context switches and thus hampers our productivity. Apart from developers’ time, there is also a financial cost involved in running the workflow over CD pipelines. An increased build time thus increases the financial burden also on the organisation.
We will be discussing some ways to reduce this build time and the best practices to reduce build time.
Starting with measuring build time
To display the build time correctly and more efficiently in the Xcode,
Run the following command on the terminal:
1defaults write com.apple.dt.Xcode ShowBuildOperationDuration YES
On opening Xcode, we will be able to see the build time at the end of the compilation success message.

What makes build time slow?
Is it our code or is it something regarding project settings? Before making any conclusion, we should make use of Build Time Analyzer for Xcode Xcode plugin, created by Robert Gummesson which tells you visually how long each part of your code took to compile.
Below are the commands needed to add in Project Target Build Settings under Other Swift Flags for Debug configuration:

1 2 3 4 5 6 7 8// This command will give a warning when a method/function is taking time >100ms -Xfrontend -warn-long-function-bodies=100 // This command will give a warning when an expression is taking time >50ms -Xfrontend -warn-long-expression-type-checking=50 // This command will help in generating logs with the build-time taken by method on Build Time Analyser Tool -Xfrontend -debug-time-function-bodies
NOTE: According to Swift.org: -Xfrontendevery front-end argument is considered unstable. All of them. Even the ones that are the same as driver options. I'd really like to rename this but I don't know what to rename it to. (And to make matters worse, Xcode currently uses it for something. You don't want to know.)
So first, make sure you are using these flags only inDEBUGmode, and second, remove the above commands from Other Swift Flags after fixing the issues you were looking for and try them from time to time.
Code Optimisation
Reduce Multiple Type Checking
It means the compiler is type checking multiple times. To reduce multiple type checking of method/property/expression, we can do the following:
- Make them
finalOR - Make them
private
This is how the compiler knows you are not going to override it, so it can type check them just once.
For instance, in our ShareChat iOS Project:
By just making the classes final, the build time of the project was reduced by 12.5%
Removing Dead/Unused code
Dead/Unused code can lead to an unnecessary increase in build time. It’s better to remove dead code from the project.
This ruby script file can help in detecting unused class, struct, methods, enums, variables etc.
💡 NOTE: Keep in mind the result of tool may have a lot of false positives (protocols, functions, and system delegates), so be careful when deleting any code.
Fixing all warnings from the project
This can help in building the project faster and helps the compiler take less time in finding out warnings.
Common Do's and Don'ts:
1. Specifying the closure used
Properly specifying the closure type for the method.
Do
1Observable() .bind(onNext: {})
Don’t
1Observable() .bind { }
Happy News:
Build time of bind closure block improved by 50%
2. Using proper type inference
Don’t let the compiler infer the type, as the compiler will take time to recognise the type and it will increase build time of the project.
Do
1 2 3let someValue: CGFloat = 10 let numbersArray: [Int] = [1, 2, 3, 4, 5] let someDictionary: [String: Int] = ["a": 1, "b": 2, "c": 3]
Don’t
1 2 3let someValue = 10 let numbersArray = [1, 2, 3, 4, 5] let someDictionary = ["a": 1, "b": 2, "c": 3]
Happy News:
Build time of expression/methods improved by ~90%
3. Avoid single line computations
We should break the code into multiple lines to increase readability and reduce the build time.
Do
1 2 3 4let numberArray: [Int] = [1, 2, 3] let stringNumArray: [String] = numberArray.map { String(describing: $0) } let intNumberArray: [Int] = stringNumberArray.compactMap { Int($0) } let sum: Int = intNumberArray.reduce(0, +)
Don't
1let sum = [1, 2, 3].map { String($0) }.compactMap { Int($0) }.reduce(0, +)
Happy News:
Build time of expression improved by ~95%
4. Avoid typecasting
Avoid casting values to their own type (It will always double/triple your build time for that expression which will contribute to overall build time)
Do
1 2 3let totalParts: CGFloat = 5 let totalWidth: CGFloat = UIScreen.main.bounds.width / totalParts let height: CGFloat = totalWidth
Don't
1 2 3let totalParts = 5 let totalWidth: CGFloat = UIScreen.main.bounds.width / CGFloat(totalParts) let height = CGFloat(totalWidth)
Happy News:
Build time of expressions/method improved by ~90%
5. Avoid using Prefix Operators directly with variables
We should avoid the use of prefix operators with variables to set any type of value.
Do
1 2 3 4let leftPadding: CGFloat = 5 let rightPadding: CGFloat = 10 leadingConstraint?.update(offset: -1 * leftPadding) trailingConstraint?.update(offset: -1 * rightPadding)
Don't
1 2 3 4let leftPadding: CGFloat = 5 let rightPadding: CGFloat = 10 leadingConstraint?.update(offset: -leftPadding) trailingConstraint?.update(offset: -rightPadding)
Happy News:
Build time of expression improved by ~96%
6. Use if-else over ternary operators
Always prefer the if-else operator over the ternary operator
Do
1 2 3 4 5 6let myName: String if isFoo { myName = "Foo" } else { myName = "Bar" }
Don’t
1let myName: String = isFoo ? "Foo" : "Bar"
Happy News:
Build time of methods improved by ~40ms to ~70ms
7. Avoid using nil Coalescing Operator (??)
Avoiding the use of nil coalescing operator in complex calculation.
Do
1 2 3 4 5 6 7 8var padding: CGFloat = 20 if let rightView = rightView { padding += rightView.bounds.width } if let leftView = leftView { padding += leftView.bounds.width } return CGSize(width: size.width + padding, height: bounds.height)
Don’t
1return CGSize(width: size.width + (rightView?.bounds.width ?? 0) + (leftView?.bounds.width ?? 0) + 20, height: bounds.height)
Happy News:
Build time of expression/method improved by ~94%
8. Use String Interpolation over String Concatenate
It is highly recommended to use string interpolation instead of the + operator.
Do
1 2 3let myHandleName: String = "johny_xyz" let followerCount: Int = 100 return "@ \(myHandleName) with \(followerCount) followers"
Don’t
1 2 3let myHandleName: String = "johny_xyz" let followerCount: Int = 100 return "@" + myHandleName + " with " + String(followerCount) + " followers"
Happy News:
Build time of methods improved by ~94%
💡 NOTE: String Interpolation gives the result by accessing the variable description on the item. String(item) calles the initialiser of the String and returns a String value, but in case if String extension with custom initialization is created, it is not guaranteed that value would be the same for String Interpolation and String Initialiser.
9. Avoid Short-hand syntax enums
Being more specific about the type of enum to be used.
Do
1 2 3 4 5var label = UILabel() label.font = UIFont.systemFont(ofSize: 16, weight: UIFont.Weight.bold) label.textColor = UIColor.red label.textAlignment = NSTextAlignment.center label.lineBreakMode = NSLineBreakMode.byWordWrapping
Don’t
1 2 3 4 5var label = UILabel() label.font = .systemFont(ofSize: 16, weight: .bold) label.textColor = .red label.textAlignment = .center label.lineBreakMode = .byWordWrapping
Happy News:
Build time of methods/expressions improved by ~98%
10. Avoid .init() while initialising/assigning new value
Using the proper type to initialise the variable.
Do
1 2let frame: CGRect frame = CGRect(origin: CGPoint.zero, size: CGSize.zero)
Don’t
1 2let frame: CGRect frame = .init(origin: .zero, size: .zero)
Happy News:
Build time of methods/expression improved by ~50ms - 70ms
11. Literal Evaluation
Use !boolValue over boolValue == false because:
- Logical value doesn't need useless type conversion, and
- ! is a static method of its
Booltype. This is enough to evaluateBoolvalue.
Do
1 2 3 4 5 6 7 8let buttonSize: CGSize = CGSize(width: 100, height: 100) let frame: CGRect = CGRect(origin: CGPoint.zero, size: buttonSize) let button = UIButton(frame: frame) if !button.isSelected { print("Button is selected") } else { print("Button is not selected") }
Don’t
1 2 3 4 5 6 7 8let buttonSize: CGSize = CGSize(width: 100, height: 100) let frame: CGRect = CGRect(origin: CGPoint.zero, size: buttonSize) let button = UIButton(frame: frame) if button.isSelected == false { print("Button is selected") } else { print("Button is not selected") }
Happy News:
Build time of methods/expression improved by ~40ms - 70ms
12. Avoid using Storyboards
Creating UI can be a tedious job, creating all the UI from Storyboards can contribute to an increase in build time. UI created using programmatically can help in the following ways:
- More code readability.
- Removing/adding/changing layouts or views easily
- Most importantly, reducing build time by a great amount.
Conclusion
- Optimise Build Settings
- Use
finalkeywords wherever required - Remove dead/unused code
- Analyse the code areas that take too much time to compile
- Use proper access modifiers
- Specify type inference instead of letting the compiler infer
- Avoid single line computations
- Avoid typecast whenever possible
- Avoid use of nil coalescing operator
- Use
if-elseover ternary operator - Use String interpolation over String concatenation
- Use proper initialiser name over
.init() - Use proper literal for evaluation of boolean expressions
- Create UI with programmatically over adding Storyboards
References
- https://medium.com/@RobertGummesson/regarding-swift-build-time-optimizations-fc92cdd91e31
- https://medium.com/swift-programming/swift-build-time-optimizations-part-2-37b0a7514cbe
- https://gist.github.com/tylermilner/84d4f88b03c21ce6ec488e72505bec53
Cover illustration by Ritesh Waingankar




