Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Developing modular apps on iOS

Developing modular apps on iOS

Slides from my talk at NSPresenter Madrid on February 28th

B0a336761194918a853deeff1f22b537?s=128

Pedro Piñera Buendía

February 26, 2018
Tweet

Transcript

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

    @pepibumur 1
  2. About me — Production Engineer at Shopify. — Building tools

    for mobile developers. — Born and raised in the ❤ Murcia. — Suffering the ☔ in Berlin. — # ppinera.es — $ @pepibumur — % pedro.pinera@shopify.com 2
  3. What/why/how should I modularize my app? @pepibumur 3

  4. What @pepibumur 4

  5. "Modularizing an app consists on organising your app code and

    resources in multiple modules" @pepibumur 5
  6. 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
  7. Static linking - Compilation time Dynamic linking - Startup time

    @pepibumur 7
  8. Dynamic modules need to be copied into the product for

    dylib to link them @pepibumur 8
  9. Static modules not properly linked might lead to duplicated symbols

    @pepibumur 9
  10. Carthage & CocoaPods are two examples of tools that modularise

    your dependencies Carthage: Dynamic linking CocoaPods: Static & dynamic linking @pepibumur 10
  11. 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
  12. Why @pepibumur 12

  13. If you are a freelancer You might want to reuse

    code @pepibumur 13
  14. If you have multiple products !⌚# You might want to

    reuse code (e.g. Shopify has 5 iOS apps) @pepibumur 14
  15. If you want to write weakly coupled components You can

    leverage modules interfaces (internal by default in Swift) @pepibumur 15
  16. If you want to speed up your workflow ⏰ And

    spend less time waiting for the compiler @pepibumur 16
  17. The compile time is proportional to the size of the

    codebase @pepibumur 17
  18. But... Apple is improving the Swi! compiler The compiler builds

    incrementally (only clean builds should take more time) @pepibumur 18
  19. — ⌘ + B (10 minutes) — Change code +

    (⌘ + B) (4 Seconds) — Change something + (⌘ + B) - (Error) — ⌘ + K (I'm sure this helps) — ⌘ + B (10 minutes) @pepibumur 19
  20. — 30 developers. — 10 clean builds per day. —

    10 minutes per clean build. 1000 hours spent per month 6.25 engineers @pepibumur 20
  21. ! Fortunately, there's something you can do to avoid this

    @pepibumur 21
  22. How @pepibumur 22

  23. This is just one approach That works in my experience

    @pepibumur 23
  24. Layers Project Downsides @pepibumur 24

  25. Before anyone asks... @pepibumur 25

  26. Yes, to avoid the hassle of dealing with versioning or

    git submodules, all the modules in the same repository @pepibumur 26
  27. Microfeatures github.com/pepibumur/microfeatures-guidelines @pepibumur 27

  28. @pepibumur 28

  29. App module Navigation App lifecycle events Dependency injector @pepibumur 29

  30. // 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
  31. 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
  32. Features modules Can be product or service features @pepibumur 32

  33. 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
  34. 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
  35. Shared components are extracted into foundation modules @pepibumur 35

  36. // 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
  37. 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
  38. // 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
  39. // Module: UI public class Color {} public class Font

    {} public class CustomView {} @pepibumur 39
  40. @pepibumur 40

  41. Examples app Allow developers to try out the features that

    they build @pepibumur 41
  42. 1 Workspace Multiple projects (fewer git conflicts per project) @pepibumur

    42
  43. There are not so cool features that I didn't mention

    (until now) @pepibumur 43
  44. From the monolith to modules ! @pepibumur 44

  45. 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
  46. Maintaining more than one Xcode project @pepibumur 46

  47. 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
  48. Continuous integration (Usually clean builds) @pepibumur 48

  49. 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
  50. 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
  51. # Examples catalisis generate-xcodeproj App catalisis build Core catalisis test

    Orders @pepibumur 51
  52. 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
  53. Thanks! Slides: h!ps://goo.gl/KAC1EJ @pepibumur 53