Slide 1

Slide 1 text

2019 DevDay Growing LINE for iOS > Christopher Rogers > LINE App Dev Team4 Senior Software Engineer

Slide 2

Slide 2 text

Lines of Code 400,000 800,000 1,200,000 1,600,000 1.0.0 1.2.0 1.3.0 1.3.4 1.6.1 2.0.2 2.3.1 3.1.0 3.2.1 3.5.0 3.6.5 3.8.2 3.9.3 4.0.1 4.3.1 4.5.0 4.7.1 4.9.1 5.0.2 5.2.1 5.4.0 5.8.0 5.10.0 6.1.1 6.4.0 6.6.1 6.8.5 6.9.2 7.1.2 7.3.0 7.5.0 7.8.0 7.12.0 7.15.0 8.0.0 8.2.1 8.4.1 8.6.0 8.9.0 8.12.1 8.16.1 8.19.0 9.0.2 9.5.0 9.7.1 9.15.1 9.18.0 Objective C Swift

Slide 3

Slide 3 text

Agenda > Dependencies > Launch Time > Bridging Objective-C & Swift > Xcode Project Size > Build Times

Slide 4

Slide 4 text

Dependencies

Slide 5

Slide 5 text

Emerging Problems > Linker settings > Library updates > Search paths

Slide 6

Slide 6 text

Ƃ CocoaPods > Builds/cleans alongside project > Integrates well with Xcode

Slide 7

Slide 7 text

Swift

Slide 8

Slide 8 text

Ƃ Carthage > Pre-builds frameworks > No integration with Xcode > Little to no configuration > Two dependency managers…

Slide 9

Slide 9 text

Hard Problems in Computer Science Caching With Carthage > Remote build artifact caching with Rome > Cache poisoning > Local caches often broke

Slide 10

Slide 10 text

Launch Time

Slide 11

Slide 11 text

Dynamic Frameworks > Solution: make them static frameworks! > Downsides to static frameworks • Cannot bundle resources • Code reuse between executables results in increased binary size > Dynamic linking (usually) happens on launch

Slide 12

Slide 12 text

Ƃ How To Create > Create dynamic framework > Mach-O type: Static Library > Do not embed > Resources

Slide 13

Slide 13 text

Ƃ Mixing It Up > Public headers become part of module alongside Swift > Enable Clang modules if needed • CLANG_ENABLE_MODULES • DEFINES_MODULE • PRODUCT_MODULE_NAME

Slide 14

Slide 14 text

Ƃ Public Headers > “Headers” Build Phase > File Inspector > Target Membership

Slide 15

Slide 15 text

Ƃ Module Maps > MODULEMAP_FILE

Slide 16

Slide 16 text

Ƃ Module Maps > MODULEMAP_FILE > umbrella makes directory contents part of module

Slide 17

Slide 17 text

Ƃ Module Maps > MODULEMAP_FILE > umbrella makes directory contents part of module > export controls if imported modules are implicitly imported

Slide 18

Slide 18 text

Launch Time 0 ms 2,000 ms 4,000 ms 6,000 ms 8,000 ms 10,000 ms 8.4.0 8.5.0 20%ile 50%ile mean 80%ile

Slide 19

Slide 19 text

Bridging Objective-C & Swift

Slide 20

Slide 20 text

MyObjCSwiftClass+CategoryName.h Objective-C Categories @class MyObjCSwiftClass; @interface MyObjCSwiftClass (CategoryName)

Slide 21

Slide 21 text

MyObjCSwiftClass+CategoryName.h Objective-C Categories // import generated header, which contains MyObjCSwiftClass #import "LINE-Swift.h" @interface MyObjCSwiftClass (CategoryName)

Slide 22

Slide 22 text

Bridging Objective-C Categories on Swift Types To Swift > Circular dependency • Generated header (MyApp-Swift.h) • Bridging header (MyApp-Bridging-Header.h) Problematic Code

Slide 23

Slide 23 text

> @objc(MyObjCSwiftClass) class MySwiftClass { } > @class MyObjCSwiftClass; can fail to bridge back to MySwiftClass (rare) > My fix (#27682) should land in Swift 5.2 Bridging Swift Classes Back To Swift via Objective-C Bridging Objective-C Categories on Swift Types To Swift > Circular dependency • Generated header (MyApp-Swift.h) • Bridging header (MyApp-Bridging-Header.h) Problematic Code

Slide 24

Slide 24 text

Module Map Workaround

Slide 25

Slide 25 text

LINE.modulemap Module Map Settings module LINE { header "LINE-Partial-Swift.h" }

Slide 26

Slide 26 text

Build Settings Module Map Settings SWIFT_INCLUDE_PATHS = “$(PROJECT_DIR)/LINE/PartialModule"

Slide 27

Slide 27 text

MyObjCSwiftClass+CategoryName.h Importing in Header // import generated header, which contains MyObjCSwiftClass #import "LINE-Swift.h" @interface MyObjCSwiftClass (CategoryName)

Slide 28

Slide 28 text

MyObjCSwiftClass+CategoryName.h Importing in Header #ifdef __swift__ @import LINE; #else // import generated header, which contains MyObjCSwiftClass #import "LINE-Swift.h" #endif @interface MyObjCSwiftClass (CategoryName)

Slide 29

Slide 29 text

Keeping in Sync { "MessageViewController.swift": [“MessageViewController”], "ShareActivity.swift": ["ShareActivity"], }

Slide 30

Slide 30 text

Xcode Project Size

Slide 31

Slide 31 text

Merge Conflicts > Requires understanding Xcode’s project file format Xcode Project Size

Slide 32

Slide 32 text

> Was this a part of the target template? > Why was this setting needed? Understanding What Has Been Configured Merge Conflicts > Requires understanding Xcode’s project file format Xcode Project Size

Slide 33

Slide 33 text

> Was this a part of the target template? > Why was this setting needed? Understanding What Has Been Configured Slow > Modifying anything was painfully slow Merge Conflicts > Requires understanding Xcode’s project file format Xcode Project Size

Slide 34

Slide 34 text

Ƃ XcodeGen > YAML config file > Can write comments > Less conflicts > 88k lines of Xcode configuration are now ~1,200 lines of YAML. > Can’t use Xcode to edit project

Slide 35

Slide 35 text

Build Times

Slide 36

Slide 36 text

> Xcode sometimes decides to rebuild everything > Incremental builds in Swift sometimes too coarse grained Tooling Problems CocoaPods > Unnecessarily rebuilt after cleaning Generated Header (LINE-Swift.h) > Nearly a clean build when modified Problem Areas

Slide 37

Slide 37 text

Precompiling Generated Header > Mileage may vary > Usually not necessary nor recommended Quick Improvements

Slide 38

Slide 38 text

> Extended type gets marked dirty when extension modified > Files marked dirty will propagate dirty flag to all of its extensions > OTHER_SWIFT_FLAGS = -driver-show-incremental Limiting Access to Swift Extensions Precompiling Generated Header > Mileage may vary > Usually not necessary nor recommended Quick Improvements

Slide 39

Slide 39 text

And Incremental Compilation Swift Extensions class MyClass { func foo() { IndexSet().bar() } } extension IndexSet { func bar() {} }

Slide 40

Slide 40 text

And Incremental Compilation Swift Extensions class MyClass { func foo() { IndexSet().bar() } } extension IndexSet { fileprivate func bar() {} }

Slide 41

Slide 41 text

And Incremental Compilation Swift Extensions class MyClass { func foo() { IndexSet().bar() } } extension IndexSet { func bar() {} }

Slide 42

Slide 42 text

> Including dependencies managed by CocoaPods & Carthage Build & Cache Modules With Bazel Split Up Codebase Into Modules > Currently 224 modules > Categorize modules Long-Term Approach

Slide 43

Slide 43 text

> Specific category for extension-safe modules Code Size for App Extensions Understanding Dependency Graph > What is OK to import? > Where to put new code? Module Categories

Slide 44

Slide 44 text

> Restriction: Cannot import feature-private modules > Small, focused > Includes feature-public modules Others Module Naming Convention > Module categories have set prefixes and/or suffixes Feature-Private (Feature) > Restriction: Cannot import other features’ private modules > Example features: Messaging, Timeline, Calls, Pay Module Categories

Slide 45

Slide 45 text

Ƃ ImportLint

Slide 46

Slide 46 text

Benefits of Bazel Remote Caching Reproducible Fast

Slide 47

Slide 47 text

> Reading Package.swift for build settings Build Code From SwiftPM in Bazel Replace CocoaPods & Carthage With SwiftPM for Dependency Resolution > Using swift package resolve Building With Bazel

Slide 48

Slide 48 text

Replace CocoaPods & Carthage With SwiftPM for Dependency Resolution > Done, but removed SwiftPM due to reliability issues (mostly with CI builds) (SR-10718) > Now Bazel is downloading hard-coded dependencies Current Status

Slide 49

Slide 49 text

> Manually created Bazel BUILD files for all dependencies Build Code From SwiftPM in Bazel Replace CocoaPods & Carthage With SwiftPM for Dependency Resolution > Done, but removed SwiftPM due to reliability issues (mostly with CI builds) (SR-10718) > Now Bazel is downloading hard-coded dependencies Current Status

Slide 50

Slide 50 text

> Manually created Bazel BUILD files for all dependencies Build Code From SwiftPM in Bazel Bonus: Move Code Generation to Bazel > Mostly done > No longer have to codegen unnecessarily Replace CocoaPods & Carthage With SwiftPM for Dependency Resolution > Done, but removed SwiftPM due to reliability issues (mostly with CI builds) (SR-10718) > Now Bazel is downloading hard-coded dependencies Current Status

Slide 51

Slide 51 text

Clean Build Times No Cache With Cache 0 6m 12m 18m 24m 30m Bazel Xcode

Slide 52

Slide 52 text

Improve Developer Experience > Code viewer & breakpoints working in Xcode with Tulsi > Code completion is not complete > Making modifications to code built by Bazel not great Remaining Issues

Slide 53

Slide 53 text

> Split up codebase into more modules > Needed for true gains in incremental build times Build More in Bazel Improve Developer Experience > Code viewer & breakpoints working in Xcode with Tulsi > Code completion is not complete > Making modifications to code built by Bazel not great Remaining Issues

Slide 54

Slide 54 text

> Split up codebase into more modules > Needed for true gains in incremental build times Build More in Bazel Cache Reusability > Some differences between machines prevent build cache reuse > Mostly resolved Improve Developer Experience > Code viewer & breakpoints working in Xcode with Tulsi > Code completion is not complete > Making modifications to code built by Bazel not great Remaining Issues

Slide 55

Slide 55 text

Thank You Also Check Out 15:30-15:40 Short Track - “Faster iOS Builds With Bazel”