ShareChat
Moj
QuickTv

iOS Build Time Reduction

Ashutosh Lasod

Ashutosh Lasod11 Apr, 2022

Follow us on FacebookFollow us on TwitterFollow us on InstagramFollow us on Linkedin
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:

1
defaults 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: -Xfrontend every 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 in DEBUG mode, 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 final OR
  • 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

1
Observable() .bind(onNext: {})


Don’t

1
Observable() .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
3
let 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
3
let 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
4
let 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

1
let 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
3
let totalParts: CGFloat = 5
let totalWidth: CGFloat = UIScreen.main.bounds.width / totalParts
let height: CGFloat = totalWidth

Don't

1
2
3
let 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
4
let leftPadding: CGFloat = 5
let rightPadding: CGFloat = 10
leadingConstraint?.update(offset: -1 * leftPadding)
trailingConstraint?.update(offset: -1 * rightPadding)


Don't

1
2
3
4
let 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
6
let myName: String
if isFoo {
    myName = "Foo"
} else {
    myName = "Bar"
}

Don’t

1
let 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
8
var 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

1
return 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
3
let myHandleName: String = "johny_xyz"
let followerCount: Int = 100
return "@ \(myHandleName) with \(followerCount) followers"

Don’t

1
2
3
let 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
5
var 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
5
var 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
2
let frame: CGRect
frame = CGRect(origin: CGPoint.zero, size: CGSize.zero)


Don’t

1
2
let 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 Bool type. This is enough to evaluate Bool value.

Do

1
2
3
4
5
6
7
8
let 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
8
let 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

  1. Optimise Build Settings
  2. Use final keywords wherever required
  3. Remove dead/unused code
  4. Analyse the code areas that take too much time to compile
  5. Use proper access modifiers
  6. Specify type inference instead of letting the compiler infer
  7. Avoid single line computations
  8. Avoid typecast whenever possible
  9. Avoid use of nil coalescing operator
  10. Use if-else over ternary operator
  11. Use String interpolation over String concatenation
  12. Use proper initialiser name over .init()
  13. Use proper literal for evaluation of boolean expressions
  14. Create UI with programmatically over adding Storyboards

References

  1. https://medium.com/@RobertGummesson/regarding-swift-build-time-optimizations-fc92cdd91e31
  2. https://medium.com/swift-programming/swift-build-time-optimizations-part-2-37b0a7514cbe
  3. https://gist.github.com/tylermilner/84d4f88b03c21ce6ec488e72505bec53


Cover illustration by Ritesh Waingankar

Other Suggested Blog

Are you in search of a job profile that fits your skill set perfectly?

Congratulations! You’ve reached the right place!

We are enroute to building a team of humble, yet ambitious folks. Grow professionally in your career with us, as we offer tremendous room for growth in unique career fields. Do not miss out on this unique opportunity. Send us your resume today!