Optimizing build time for an iOS project
This document presents in detail some of the best practices when writing code in a way that can help the team improve build time drastically on iOS. It includes:
- Environment used and test code for performing the build time statistic recording.
- Dos and don’ts on several implementation points in iOS development with actual build time results.
- Good resources and tools for later reference.
Environment Specifications
All the build time performance recordings are performed on the following environment specifications:
-
Laptop: MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports).
Processor: 2 GHz Quad-Core Intel Core i5.
Memory: 16 GB 3733 MHz LPDDR4X.
Graphic: Intel Iris Plus Graphics 1536 MB.
-
IDE: Xcode Version 13.4.1 (13F100).
Best Practices
Here are some of the common implementation points that iOS developers usually run into and they often choose to write code in a shorter way. Sometimes, shorter code poses some adverse effects on the build time that can be avoided with the following practices:
Complex Operations
- Separate complex operations into a new variable and define the value type for array/dictionary item explicitly.
Example - mapping array with calculation
// Cumulative build time: 79.1 ms
indexedChartViewModel.update(
withXYs: values.enumerated().map { (Double($1.x * 7.0), $0) },
updatedAt: updatedAt,
animated: animated
)
// Cumulative build time: 8.0 ms
let xyPoints = values.enumerated().map { (Double($1.x * 7.0), $0) }
indexedChartViewModel.update(
withXYs: xyPoints,
updatedAt: updatedAt,
animated: animated
)
Example - calculating numbers
// Cumulative build time: 41.5 ms
NSLayoutConstraint
.activate([
imageView.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 0.0),
imageView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 4.0),
imageView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor, constant: 4.0),
imageView.heightAnchor.constraint(equalToConstant: floor((parentView.bounds.width - 6.0) * 282.0 / 343.0))
])
// Cumulative build time: 3.5 ms
let ratio: CGFloat = 282.0 / 343.0
let imageWidth: CGFloat = parentView.bounds.width - 6.0
let calculatedHeight: CGFloat = floor(imageWidth * ratio)
NSLayoutConstraint
.activate([
imageView.topAnchor.constraint(equalTo: parentView.topAnchor, constant: 0.0),
imageView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: 4.0),
imageView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor, constant: 4.0),
imageView.heightAnchor.constraint(equalToConstant: calculatedHeight)
])
Example - handling condition
// Cumulative build time: 13.0 ms
let activities = activity.activities ?? []
activities.forEach { storeUserActivity(with: $0) }
if !activities.contains(where: { $0 == .dolfinWalletCoachMark }) {
userDefault.removeObject(forKey: UserActivity.ActivityType.dolfinWalletCoachMark.rawValue)
}
if !activities.contains(where: { $0 == .dolfinWalletOnboarding }) {
userDefault.removeObject(forKey: UserActivity.ActivityType.dolfinWalletOnboarding.rawValue)
}
// Cumulative build time: 8.8 ms
let activities: [UserActivity.ActivityType] = activity.activities ?? []
var isContainsDolfinWalletCoachMark = false
var isContainsDolfinWalletOnboarding = false
for activity in activities {
storeUserActivity(with: activity)
if activity == .dolfinWalletCoachMark { isContainsDolfinWalletCoachMark = true }
if activity == .dolfinWalletOnboarding { isContainsDolfinWalletOnboarding = true }
}
if !isContainsDolfinWalletCoachMark { userDefault.removeObject(forKey: UserActivity.ActivityType.dolfinWalletCoachMark.rawValue) }
if !isContainsDolfinWalletOnboarding { userDefault.removeObject(forKey: UserActivity.ActivityType.dolfinWalletOnboarding.rawValue) }
String Addition
- Use String interpolation instead of concatenation when adding new texts to an existing string.
// Cumulative build time: 44.9 ms
imageView.image = UIImage(named: path + "/Normal")
// Cumulative build time: 4.4 ms
imageView.image = UIImage(named: "\(path)/Normal")
Array Item Addition
- Prefer to use array concatenation instead of appending when adding a new item to an existing array.
// Cumulative build time: 60.9 ms
let nameIndexes = [1, 2, 3, 4, 5]
let count = nameIndexes.count - 1
var names = nameIndexes.map { String(format: arrayNameFormat, $0) }
names.append(NSLocalizedString("everything", comment: ""))
arrayNames = Array(names[0..<count])
if let lastName = names.last {
arrayNames.append(lastName)
}
// Cumulative build time: 3.1 ms
let nameIndexes = [1, 2, 3, 4, 5]
let count = nameIndexes.count - 1
let names = nameIndexes.map { String(format: arrayNameFormat, $0) } + [NSLocalizedString("everything", comment: "")]
if let lastName = names.last {
arrayNames = Array(names[0..<count]) + [lastName]
}
Nil Coalescing Operator
- Unwrap the variables or views instead of using optional chaining for lengthy operations.
// Cumulative build time: 127.3 ms
return CGSize(width: bounds.size.width, height: bounds.size.height + (topView?.bounds.size.height ?? 0) + (bottomView?.bounds.size.height ?? 0) + 10)
// Cumulative build time: 3.9 ms
var verticalPadding: CGFloat = 10
if let topView = topView {
verticalPadding += topView.bounds.size.width
}
if let bottomView = bottomView {
verticalPadding += bottomView.bounds.size.width
}
return CGSize(width: bounds.size.width, height: bounds.size.height + verticalPadding)
Ternary Operator
- Prefer to use if else block instead of ternary operator, especially for complex assignments or operations with nested calculations.
// Cumulative build time: 27.8 ms
systemNames = systemType == 0 ? (1...9).map { String(format: systemType0StringFormat, $0) } : (2...9).map { String(format: systemType1StringFormat, $0) }
// Cumulative build time: 2.7 ms
if systemType == 0 {
systemNames = (1...9).map { String(format: systemType0StringFormat, $0) }
} else {
systemNames = (2...9).map { String(format: systemType1StringFormat, $0) }
}
Casting CGFloat
- Avoid redundant CGFloat casting whenever possible.
// Cumulative build time: 107.6 ms
return CGFloat(Double.pi) * (CGFloat((hour + hourDelta + CGFloat(minute + minuteDelta) / 60) * 5) - 15) * unit / 180
// Cumulative build time: 19.1 ms
return CGFloat(Double.pi) * ((hour + hourDelta + (minute + minuteDelta) / 60) * 5 - 15) * unit / 180
Round() Function
- Only use
Round()
function when it is really needed.
// Cumulative build time: 41.9 ms
let finalValue = tValue - wValue - xValue + round(yValue * 0.66) + zValue
// Cumulative build time: 2.6 ms
let finalValue = tValue - wValue - xValue + (yValue * 0.66) + zValue
Build Time Recordings Report
Here is an actual report on the accumulative build time for all the listed cases above:
There is also a repository setup with actual code for the above examples. Please don’t hesitate to give it a try to build and follow the instructions from the Build Time Analyzer App to see the generated reports. 💪
Resources
These are all the dedicated resources used and referred from to come up with the above practices:
- GitHub MacOS Application: Build Time Analyzer App
- GitHub Example Code: Build Time Analyzer Recordings
- GitHub Document: Optimizing Swift build times
- Betterprogramming Document: How To Boost Xcode’s Compile Time and Runtime
- Medium Document: Regarding Swift build time optimizations