Slide 1

Slide 1 text

Developing modular apps on iOS NSPresenter Madrid - February 28th @pepibumur 1

Slide 2

Slide 2 text

About me — Production Engineer at Shopify. — Building tools for mobile developers. — Born and raised in the ❤ Murcia. — Suffering the ☔ in Berlin. — # ppinera.es — $ @pepibumur — % [email protected] 2

Slide 3

Slide 3 text

What/why/how should I modularize my app? @pepibumur 3

Slide 4

Slide 4 text

What @pepibumur 4

Slide 5

Slide 5 text

"Modularizing an app consists on organising your app code and resources in multiple modules" @pepibumur 5

Slide 6

Slide 6 text

A module can be — A library: If it doesn't have resources. — A framework: If it has them. — Static: If it's linked at compile time. — Dynamic: If it's linked at startup time. @pepibumur 6

Slide 7

Slide 7 text

Static linking - Compilation time Dynamic linking - Startup time @pepibumur 7

Slide 8

Slide 8 text

Dynamic modules need to be copied into the product for dylib to link them @pepibumur 8

Slide 9

Slide 9 text

Static modules not properly linked might lead to duplicated symbols @pepibumur 9

Slide 10

Slide 10 text

Carthage & CocoaPods are two examples of tools that modularise your dependencies Carthage: Dynamic linking CocoaPods: Static & dynamic linking @pepibumur 10

Slide 11

Slide 11 text

Most of apps are usually built and grown as monoliths 1 target with the app source 1 target with the tests (And maybe some extensions) @pepibumur 11

Slide 12

Slide 12 text

Why @pepibumur 12

Slide 13

Slide 13 text

If you are a freelancer You might want to reuse code @pepibumur 13

Slide 14

Slide 14 text

If you have multiple products !⌚# You might want to reuse code (e.g. Shopify has 5 iOS apps) @pepibumur 14

Slide 15

Slide 15 text

If you want to write weakly coupled components You can leverage modules interfaces (internal by default in Swift) @pepibumur 15

Slide 16

Slide 16 text

If you want to speed up your workflow ⏰ And spend less time waiting for the compiler @pepibumur 16

Slide 17

Slide 17 text

The compile time is proportional to the size of the codebase @pepibumur 17

Slide 18

Slide 18 text

But... Apple is improving the Swi! compiler The compiler builds incrementally (only clean builds should take more time) @pepibumur 18

Slide 19

Slide 19 text

— ⌘ + B (10 minutes) — Change code + (⌘ + B) (4 Seconds) — Change something + (⌘ + B) - (Error) — ⌘ + K (I'm sure this helps) — ⌘ + B (10 minutes) @pepibumur 19

Slide 20

Slide 20 text

— 30 developers. — 10 clean builds per day. — 10 minutes per clean build. 1000 hours spent per month 6.25 engineers @pepibumur 20

Slide 21

Slide 21 text

! Fortunately, there's something you can do to avoid this @pepibumur 21

Slide 22

Slide 22 text

How @pepibumur 22

Slide 23

Slide 23 text

This is just one approach That works in my experience @pepibumur 23

Slide 24

Slide 24 text

Layers Project Downsides @pepibumur 24

Slide 25

Slide 25 text

Before anyone asks... @pepibumur 25

Slide 26

Slide 26 text

Yes, to avoid the hassle of dealing with versioning or git submodules, all the modules in the same repository @pepibumur 26

Slide 27

Slide 27 text

Microfeatures github.com/pepibumur/microfeatures-guidelines @pepibumur 27

Slide 28

Slide 28 text

@pepibumur 28

Slide 29

Slide 29 text

App module Navigation App lifecycle events Dependency injector @pepibumur 29

Slide 30

Slide 30 text

// App import Core class Registry { static let client = Client(url: "https://shopify.com") static let analytics = Tracker(providers: [.firebase]) static let logger = Logger() } @pepibumur 30

Slide 31

Slide 31 text

import Product import Home // Module: App class HomeCoordinator: HomeDelegate { let navigationController: UINavigationController! // HomeDelegate func didSelectProduct(id: String) { let vc = ProductFeature(id: id, client: Registry.client, analytics: Registry.analytics) .viewController navigationController.pushViewController(productViewController) } } @pepibumur 31

Slide 32

Slide 32 text

Features modules Can be product or service features @pepibumur 32

Slide 33

Slide 33 text

import Core // Module: Product public class ProductFeature { public init(id: String, client: Client, analytics: Analytics) { // Initialization } // !! We just expose UIKit classes public var viewController: UIViewController { // Initialization & dependency injection } } @pepibumur 33

Slide 34

Slide 34 text

import Core // Module: BackgroundSync public class BackgroundSyncer { public init(client: Client, store: Store) { /* Init */ } public func sync(completion: @escaping (Error?) -> Void) } // Module: App @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { Registry.backgroundSyncer.sync { error in // Notify completionHandler } } } @pepibumur 34

Slide 35

Slide 35 text

Shared components are extracted into foundation modules @pepibumur 35

Slide 36

Slide 36 text

// Module: Core public class Client: Clienting {} public class Logger: Logging {} public class Store: Storing {} public class SecureStore: SecureStoring {} // Module: CoreTesting public class MockClient: Clienting { public var executeCount: UInt! public var executeRequest: URLRequest! public var executeStub: (Any?, Error?)! } @pepibumur 36

Slide 37

Slide 37 text

import XCTest import Core import CoreTesting // ! @testable import BackgroundSync final class BackgroundSyncerTests: XCTestCase { var client: MockClient! Var store: MockStore! var subject: BackgroundSyncer! override func setUp() { super.setUp() client = MockClient() store = MockStore() subject = BackgroundSyncer(client: client, store: store) } } @pepibumur 37

Slide 38

Slide 38 text

// Module: Testing import XCTest public extension XCTestCase { public func XCTAssertTapa(_ tapa: Tapa, identifier: String? = nil, tolerance: CGFloat = 0, file: StaticString = #file, line: UInt = #line) { // Asserts the quality of the tapa } } @pepibumur 38

Slide 39

Slide 39 text

// Module: UI public class Color {} public class Font {} public class CustomView {} @pepibumur 39

Slide 40

Slide 40 text

@pepibumur 40

Slide 41

Slide 41 text

Examples app Allow developers to try out the features that they build @pepibumur 41

Slide 42

Slide 42 text

1 Workspace Multiple projects (fewer git conflicts per project) @pepibumur 42

Slide 43

Slide 43 text

There are not so cool features that I didn't mention (until now) @pepibumur 43

Slide 44

Slide 44 text

From the monolith to modules ! @pepibumur 44

Slide 45

Slide 45 text

1. Extract build se!ings into config files. 2. Extract foundation components/extensions. 3. Build new features in modules. 4. Gradually extract the existing features. ⚠ Extracting is not easy ⚠ @pepibumur 45

Slide 46

Slide 46 text

Maintaining more than one Xcode project @pepibumur 46

Slide 47

Slide 47 text

Tips to ease maintenance 1. Extract settings into .xcconfig files (a reusable single source of truth). 2. Automate the generation of Xcode projects (e.g. using XcodeGen). 3. Just Debug and Release configurations. @pepibumur 47

Slide 48

Slide 48 text

Continuous integration (Usually clean builds) @pepibumur 48

Slide 49

Slide 49 text

Tips to speed up CI builds 1. Enable parallelize builds on your schemes. 2. Selective builds on CI. Modules: - name: Search path: Search/Sources/** build: xcodebuild -workspace Search.xcworkspace -scheme Search dependencies: - Core - name: Core path: Core/Sources/** build: xcodebuild -workspace Core.xcworkspace -scheme Core @pepibumur 49

Slide 50

Slide 50 text

Catalisis (coming soon) — Command line tool. — Generates workspaces and projects for the module you are working on. — Caches modules to save time on CI and locally. — Offers an API to build/test modules. @pepibumur 50

Slide 51

Slide 51 text

# Examples catalisis generate-xcodeproj App catalisis build Core catalisis test Orders @pepibumur 51

Slide 52

Slide 52 text

Conclusions — Do it of you really need it. — (reuse or decouple code, save time) — More independent and productive teams. — Apple is also improving things. — It comes with some costs and lack of tooling (tools are optimized for monoliths) — A transition into modules is tough. @pepibumur 52

Slide 53

Slide 53 text

Thanks! Slides: h!ps://goo.gl/KAC1EJ @pepibumur 53