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

Post on 15-Apr-2017

1200 Views

Category:

Mobile

2 Downloads

Preview:

Click to see full reader

Transcript

Speed up build time of big project on

Objective-C + SwiftIvan Bondar

Lead iOS developer in 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

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

Tune build settingsBuild Active Architecture Only

Enable Objective-C Modules

Debug data format - DWARF (no dsym file)

Enable Whole module optimization

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

Build time: 191.623s (194.803s before)

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

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

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

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/

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)

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

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.

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 }()

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

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

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 } }()

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

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

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

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)

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!

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

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

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

running app + support «tunable parameters»

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

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.

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/

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

Plans

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:

Questions?

top related