"Ускорение сборки большого проекта на Objective-C + Swift" Иван...

29
Speed up build time of big project on Objective-C + Swift Ivan Bondar Lead iOS developer in Avito

Transcript of "Ускорение сборки большого проекта на Objective-C + Swift" Иван...

Page 1: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Speed up build time of big project on

Objective-C + SwiftIvan Bondar

Lead iOS developer in Avito

Page 2: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Build time: 194.803s

75% of build time - Compile Swift files phase

Swift: - *.swift files: 626 - LOC: 27264

Project structureObjective-C:

- *.m files: 729 - LOC: 45947 - 217 imports in bridging-header

Page 3: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

What to do• Tune build settings • Reduce .swift files count • Reduce extensions count • Optimize slow compiling functions • Fix warnings • Apply ccache compiler cache utility

Page 4: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Tune build settingsBuild Active Architecture Only

Enable Objective-C Modules

Debug data format - DWARF (no dsym file)

Enable Whole module optimization

Page 5: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Tune build settingsDebug data format - DWARF (no dsym file)

Build time: 191.623s (194.803s before)

Page 6: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Tune build settingsWhole module optimization

Main target build time: 76.614 s Not suitable for debuggingCan’t compile unit test target - segfault or weird errors with Swift-ObjC bridging

Page 7: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Reduce .swift files countPre-build action - merge all Swift code to one FAT Swift file

Not suitable for big projects: - can’t compile with segmentation fault 11 - eliminates «private» modifier - can’t use breakpoints in the original source

Page 8: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Reduce .swift files countMerge different classes/protocols in big .swift files

Not suitable for VIPER in general Decided to apply only in certain cases, e.g. put Input and Output protocol declarations in one file

Page 9: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Reduce extensions count

0

10

20

30

40

100 1000 2000 3000 5000 10000methods extensions

* by Dmitry Bespalov https://tech.zalando.com/blog/speeding-up-xcode-builds/

Page 10: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Reduce extensions count

class FilterViewController: UIViewController { }

// MARK: UITableViewDelegate extension FilterViewController: UITableViewDelegate { }

// MARK: UITableViewDataSource extension FilterViewController: UITableViewDataSource { }

class FilterViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { // MARK: UITableViewDelegate // MARK: UITableViewDataSource }

Changes in code style applied

Before: After:

Extensions count reduced by 400 Build time: 94.3s (191.623s before)

Page 11: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Optimize slow compiling functionsProfile compile time per function, filter by time > 1 ms.

xcodebuild ARCHS=arm64 ONLY_ACTIVE_ARCH=NO -workspace App.xcworkspace -scheme App clean build OTHER_SWIFT_FLAGS="-Xfrontend -debug-time-function-bodies" | grep [1-9].[0-9]ms | sort -nr > culprits.txt

Page 12: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Optimize slow compiling functions

What to fix: • Functions with longest compile time • Functions with big number of occurrences

2871.3ms /Users/iyubondar/Projects/avito-ios/Models/Domain/Advertisement/Advertisement/Base/AdvertisementImage.swift:2:5 init(url100x75: String?, url140x105: String?, url240x180: String?, url432x324: String?, url640x480: String?, url1280x960: String?)932.7ms /Users/iyubondar/Projects/avito-ios/Core/Extensions/UIKitExtensions/UICollectionView/UICollectionView+ChangeAnimations.swift:57:17

final class func changeSet<T : Hashable>(oldArray oldArray: [T], newArray: [T]) -> CollectionViewChangeSet253.0ms /Users/iyubondar/Projects/avito-ios/Core/Extensions/UIKitExtensions/UICollectionView/UICollectionView+ChangeAnimations.swift:90:17

final class func changeSet<T>(oldArray oldArray: [T], newArray: [T], identityHashFunction: (T) -> Int, identityCheckFunction: (T, T) -> Bool, equalityCheckFunction: (T, T) -> Bool = default) -> CollectionViewChangeSet136.0ms /Users/iyubondar/Projects/avito-ios/Presentation/Views/Advertisement/AdvertisementView/AdvertisementPresenter.swift:9:26 @objc public override func setModel(model: AnyObject!)91.4ms /Users/iyubondar/Projects/avito-ios/Presentation/Views/Controls/PullToRefresh/ScrollViewRefresher.swift:251:18 private func handleRefreshingProgressChanged(progress: RefreshingProgress)84.0ms /Users/iyubondar/Projects/avito-ios/VIPER/SelectCategoryParameters/Validation/SelectFromToValidator.swift:52:25 private final class func findValidRowIndex(valuesToSelect: [SelectCategoryParameterViewModel.Data], inCompareToValues compareValues: [SelectCategoryParameterViewModel.Data]?, selectedRowIndex: Int, iterationOrder: IterationOrder) -> Int82.5ms /Users/iyubondar/Projects/avito-ios/Presentation/Views/Profile/ProfileViewPresenter.swift:30:10 @objc func notificationsSubtitle() -> String81.8ms <invalid loc> init?(rawValue: String)currentDate: NSDate, calendar: NSCalendar, todayDateFormatter: NSDateFormatter, yesterdayDateFormatter: NSDateFormatter, weekdayDateFormatter: NSDateFormatter, dayDateFormatter: NSDateFormatter, yearDayDateFormatter: NSDateFormatter, fullDateFormatter: NSDateFormatter) -> String

8659 occurrences found.

Page 13: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Use lazy only when it's necessary

1204 occurrences found Compile time: 1.0 … 11.8 ms

Optimize slow compiling functions

private lazy var footerLabel: UILabel = { let footerLabel = UILabel() footerLabel.textColor = SpecColors.mainText footerLabel.font = SpecFonts.regular(14) footerLabel.autoresizingMask = .FlexibleWidth footerLabel.lineBreakMode = .ByWordWrapping footerLabel.numberOfLines = 0 footerLabel.textAlignment = .Center footerLabel.shadowColor = UIColor.whiteColor() footerLabel.shadowOffset = CGSize(width: 0, height: -1) return footerLabel }()

Page 14: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Avoid long expressions

Compile time: 2871.3ms

Optimize slow compiling functions

self.thumbnailUrl = url240x180 ?? url140x105 ?? url100x75 ?? nil self.fullImageUrl = url640x480 ?? url432x324 ?? url1280x960 ?? url240x180 ?? url140x105 ?? url100x75 ?? nil

self.thumbnailUrl = url240x180 ?? url140x105 ?? url100x75 self.fullImageUrl = url640x480 ?? url432x324 ?? url1280x960 ?? url240x180 ?? url140x105 ?? url100x75

Compile time: 916 ms

Page 15: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Avoid long expressionsOptimize slow compiling functions

var fullImageUrl: String? { if let url640x480 = url640x480 { return url640x480 } if let url432x324 = url432x324 { return url432x324 } … if let url100x75 = url100x75 { return url100x75 } return nil }

Compile time: <1 ms

Page 16: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Use map() and flatMap() with care

1177 occurrences found Compile time: 2.5 … 21.8 ms

Optimize slow compiling functions

private lazy var tabControllers: [UIViewController] = { var controllers = [UIViewController?](count: Tab.tabsCount, repeatedValue: nil) controllers[Tab.Search.rawValue] = self.categoriesNavigationController() controllers[Tab.Favorites.rawValue] = self.favoritesNavigationController() controllers[Tab.Publish.rawValue] = self.publishNavigationController() controllers[Tab.Messenger.rawValue] = self.channelsRootViewController() controllers[Tab.Profile.rawValue] = self.profileNavigationController() return controllers.flatMap { $0 } }()

Page 17: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Use map() and flatMap() with careOptimize slow compiling functions

Original example: http://irace.me/swift-profiling

return [CustomType()] + array.map(CustomType.init) + [CustomType()]

Compile time: 3158.2 ms and many occurrences

Page 18: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Give «type hints» to the compiler when necessary

* example by IMPATHIC http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times

Optimize slow compiling functions

func hangCompiler() { ["A": [ ["B": [ 1, 2, 3, 4, 5 ]], ["C": [ ]], ["D": [ ["A": [ 1 ]] ]] ]] }

Build time: 54.249 sfunc hangCompiler() { ["A": [ ["B": [ 1, 2, 3, 4, 5 ]] as [String: [Int]], ["C": [ ]] as [String: [Int]], ["D": [ ["A": [ 1 ]] as [String: [Int]] ]] ]] }

Build time: 1.293 s

Page 19: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Optimize slow compiling functions

Build time: 92.5s (94.3s before)

No great effect in our caseBefore:

• 8659 functions > 1ms • max time 2871.3мс • median time 1.8ms

After: • 2227 functions > 1ms • max time 124.3мс • median time 3.4ms

Page 20: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Fix warningsSwift Compiler Warnings: 64 total

Warning type Count Build time after fix

Parameters of ... have different optionality 3 92.5sUser-defined 2 92.5sCannot find protocol definition 3 90.481sOverriding instance method parameter with implicitly unwraped optional type 27 warnings 27 90.195s

Deprecation 18 93.487s<Some code> will never been executed 1 93.654sImmutable value ... was never used 1 93.152sPointer is missing nullability specifier 9 80.667s

Build time: 80.667s (92.5s before)

Page 21: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Fix warningsBut, with 1 warning <Some code> will never been executed, build time is 84.919s

Fix all Swift Compiler Warnings to speed up build time!

Page 22: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Apply ccache compiler cache utility

Build time: 68.403s (80.667s before)

ccache limitations: - no support for Clang modules - no support for precompiled headers - no Swift support

ccache applied to project

Page 23: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

What was done

Before: 194.803 s After: 68.403 s

There is no silver bullet :(• Tune build settings - 3s • Reduce .swift files count - 0s • Reduce extensions count - 97s • Optimize slow compiling functions - 2s • Fix warnings - 12s • Apply ccache compiler cache utility -12s

Page 24: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Injection for Xcode plugin:+ dynamically inserts new Swift / Objective-C code into a

running app + support «tunable parameters»

Source: https://github.com/johnno1962/injectionforxcode

Page 25: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Injection for Xcode plugin:

Source: https://github.com/johnno1962/injectionforxcode

Swift limitations. It’s not possible to: - Make changes to Structs. - Change functions or classes that are marked as final. - Change global functions or variables that are not

constrained into a class.

Page 26: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Split app into frameworks with no cyclic dependencies between classes of different frameworks *

Plans

* idea and image: http://bits.citrusbyte.com/improving-swift-compile-time/

Page 27: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Further legacy code refactoring, remove Swift - Objective C dependencies.

Plans

Page 28: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Links

• http://bits.citrusbyte.com/improving-swift-compile-time/ • http://stackoverflow.com/questions/25537614/why-is-swift-compile-time-so-slow

General:

• http://blog.impathic.com/post/99647568844/debugging-slow-swift-compile-times • http://irace.me/swift-profiling

Debug slow compile time:

• https://tech.zalando.com/blog/speeding-up-xcode-builds/ • https://gist.github.com/lucholaf/e37f4d26e406250a156a

Speed up builds:

• https://developer.apple.com/videos/play/wwdc2015/409/ • http://useyourloaf.com/blog/swift-whole-module-optimization/ • http://useyourloaf.com/blog/modules-and-precompiled-headers/ • https://labs.spotify.com/2013/11/04/shaving-off-time-from-the-ios-edit-build-test-cycle/ • http://tomj.io/2015/08/29/speed-up-your-swift-test-builds-by-70-percent.html • https://pewpewthespells.com/blog/managing_xcode.html#dep-imp

Build settings:

• https://pspdfkit.com/blog/2015/ccache-for-fun-and-profit/ • https://ccache.samba.org/manual.html#_configuration

ccache utility:

Page 29: "Ускорение сборки большого проекта на Objective-C + Swift" Иван Бондарь (Avito)

Questions?