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

大規模なアプリのマルチモジュール構成の実践

giginet
September 19, 2021

 大規模なアプリのマルチモジュール構成の実践

giginet

September 19, 2021
Tweet

More Decks by giginet

Other Decks in Technology

Transcript

  1. 5 View Model ViewController 🔥ϩδοΫΛ࣋ͭ7JFX XXXManager 🔥γϯάϧτϯͷσʔλιʔε 🔥.BTTJWF7JFX$POUSPMMFS 🔥ڞ௨Խ͞ΕͯංେԽͨ͠Ϟσϧ ☠

    🔥σουίʔυ 🔥ີ݁߹ 🔥7JFXͷܧঝɺ࢖͍ճ͠ 🈲 🔥৮ͬͯ͸ ͍͚ͳ͍΍ͭ 🌀 🔥ࠞಱ ϚϧνϞδϡʔϧԽҎલ
  2. ΫοΫύουΞϓϦ • 50 Releases / year • 15 developers /

    release • 40 ~ 50 PR / release • 328,000 lines
  3. CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature

    C 🔥 $PSFϞδϡʔϧ ந৅ԽͷͨΊͷΠϯλʔϑΣΠε ɾΞϓϦ಺Ͱڞ௨ͯ͠࢖͏6*ίϯϙʔ ωϯτͷఏڙ
  4. CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature

    C 🔥 'FBUVSF.PEVMF ڞ௨ͷυϝΠϯ૚Λ͍͔࣋ͭͭ͘ͷ 7*1&3γʔϯΛଋͶͨϞδϡʔϧ
  5. CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature

    C 🔥 ΞϓϦέʔγϣϯ "QQMJDBUJPO5BSHFUʹ૬౰ શͯͷґଘΛ஌ΕΔ
  6. CookpadCore Cookpad ɾɾɾ Cookpad Tsukuru Feature A Feature B Feature

    C 🔥 $PPLQBE 7*1&3Խ͞Ε͍ͯΔ͕ϞδϡʔϧԽ͞ Ε͍ͯͳ͍γʔϯͷू߹
  7. import Foundation import UIKit public protocol Environment { var client:

    ServiceClient { get } var pvLogger: PVLogger { get } var userFeatures: UserFeatures { get } var activityLogger: ActivityLogger { get } // … } CookpadCore ґଘΛQSPUPDPMԽ
  8. final class CookpadEnvironment: Environment { var client: ActivityLogger { CookpadActivityLogger(

    ) } // … } Cookpad ΞϓϦέʔγϣϯλʔήοτͰ ۩ମతͳ࣮૷Λฦ͢
  9. let viewController = RecipeDetails.RecipeDetailsViewController(recipeID: 42 ) present(viewController, animated: true, completion:

    nil ) Feature A 3FDJQF%FUBJMTϞδϡʔϧ಺ͷ γʔϯ͸'FBUVSF"Ϟδϡʔϧ͔ Β͸ࢀরͰ͖ͳ͍ ❌
  10. import Foundation import UIKit public protocol Environment { var client:

    ServiceClient { get } var pvLogger: PVLogger { get } var userFeatures: UserFeatures { get } var activityLogger: ActivityLogger { get } // … func resolve<Descriptor: TypedDescriptor>(_ descriptor: Descriptor) -> Descriptor.Outpu t } CookpadCore 3FTPMWFS %FTDSJQUPSΛड͚औͬͯ ࣮૷Λฦ͢ϝιου %FTDSJQUPS ࣮૷Λࢦࣔ͢͠ϚʔΧʔ
  11. public struct RecipeDetailsDescriptor: TypedDescriptor { public typealias Output = UIViewControlle

    r public var recipeID: Int6 4 public init(recipeID: Int64) { self.recipeID = recipeI D } } CookpadCore %FTDSJQUPS ࣮૷Λࢦࣔ͢͠ϚʔΧʔ
  12. final class CookpadEnvironment: Environment { public func resolve<Descriptor: TypedDescriptor>(_ descriptor:

    Descriptor) -> Descriptor.Output { switch descriptor { case let d as ViewDescriptor.RecipeDetailsDescriptor : return RecipeDetailsViewBuilder.build(with: d, environment: self) as! Descriptor.Outpu t } } } Cookpad %FTDSJQUPS͝ͱʹ࣮૷Λฦͯ͠ ΞοϓΩϟετ͢Δ
  13. let viewController: UIViewController = environment.resolve(ViewDescriptor.RecipeDetailsDescriptor(recipeID: 42) ) viewController.present(viewController, animated: true,

    completion: nil ) Feature A 3FTPMWFSܦ༝ͰऔΓग़͢͜ͱͰɺผͷϞδϡʔϧ ಺ͷγʔϯ΋6*7JFX$POUSPMMFSͱͯ͠औಘͰ͖Δ
  14. import Foundation public protocol ActivityLogger { func post(eventName: String, payload:

    [String: Any]? ) } public protocol Environment { var activityLogger: ActivityLogger { get } // … } CookpadCore
  15. import CookpadCor e private struct CookpadActivityLogger: ActivityLogger { func post(eventName:

    String, payload: [String: Any]?) { LegacyActivityLogger.shared.post(eventName: eventName, payload: payload ) } } final class CookpadEnvironment: Environment { var activityLogger: ActivityLogger { return CookpadActivityLogger( ) } } Cookpad ΞϓϦέʔγϣϯλʔήοτ͔ Βಈ͔ͤͳ͍ݹ͍࣮૷Λϥοϓ ͢Δ
  16. final class LegacyViewBuilder: ViewBuilder { func build(environment: Environment) -> UIViewController

    { // VIPERԽ͞Ε͍ͯͳ͍Objective-CͰ࣮૷͞Εͨݹ͍ViewController let legacyViewController = CKDLegacyViewController( ) return legacyViewControlle r } } Cookpad ΫϦʔϯΞʔΩςΫνϟԽ͞Ε͍ͯͳ͍ݹ͍ը໘΋ 7JFX#VJMEFSͰϥοϓ͢Δͱ3FTPMWFSܦ༝ͰऔಘͰ͖Δ
  17. targetTemplates : FeatureModule : platform: iOS type: framework sources :

    - ${frameworkName} settings : base : PRODUCT_BUNDLE_IDENTIFIER: com.cookpad.${frameworkName} MACH_O_TYPE: staticlib info : path: ${frameworkName}/Info.plist dependencies : - sdk: Foundation.framework - sdk: UIKit.framework - target: CookpadCor e prebuildScripts : - name: SwiftLint path: "configs/scripts/swift_lint_module.sh" ม਺
  18. -- - targets : MyAwesomeModule : templates : - FeatureModule

    templateAttributes : frameworkName: MyAwesomeModule ม਺ ܧঝݩͷ ςϯϓϨʔτ
  19. import CookpadCore import UIKit public final class {{ name }}ViewController:

    UIViewController, {{ name }}ViewContract { private var presenter: {{ name }}PresenterContract ! private let environment: Environmen t init(environment: Environment) { self.environment = environmen t super.init(nibName: nil, bundle: nil ) } @available(*, unavailable ) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented" ) } func inject(presenter: {{ name }}PresenterContract) { self.presenter = presente r } override public func viewDidLoad() { super.viewDidLoad( ) // Insert code here to connect presenter } ม਺
  20. # name must be given as options to Genesis options

    : - name: name question: VIPER scene name? description: new VIPER scene name to generate(e.g. RecipeDetails). type: string required: true - name: module question: Destination target? description: Target destination name to generate new VIPER scene. (e.g. RecipeDetails) type: string required: true files : - template: VIPER/FeatureModule/Regular/Contract.stencil path: "{{ module }}/Application/{{ name }}/{{ name }}Contract.swift" - template: VIPER/FeatureModule/Regular/ViewController.stencil path: "{{ module }}/Application/{{ name }}/{{ name }}ViewController.swift" - template: VIPER/FeatureModule/Regular/Wireframe.stencil path: "{{ module }}/Application/{{ name }}/{{ name }}Wireframe.swift ” … ࣭໰ͷ౴͑Λม਺ʹ֨ೲͰ͖Δ ͲͷςϯϓϨʔτΛͲ͜ʹੜ੒͢Δ͔ࢦఆ
  21. final class CookpadEnvironment: Environment { public func resolve<Descriptor: TypedDescriptor>(_ descriptor:

    Descriptor) -> Descriptor.Output { switch descriptor { case let d as ViewDescriptor.RecipeDetailsDescriptor : return RecipeDetailsViewBuilder.build(with: d, environment: self) as! Descriptor.Outpu t } } } Cookpad ࣮૷๨Ε͕͋ΔͱΫϥογϡ͢Δ ୯७ʹຖճ࣮૷͢Δͷ͕໘౗ ͜͜ʹෳࡶͳॲཧ͕ॻ͚ͯ͠·͏ ❌ ❌ ❌
  22. // ࣗಈੜ੒͢ΔΑ͏ʹͨ͠ final class CookpadEnvironment: Environment { public func resolve<Descriptor:

    TypedDescriptor>(_ descriptor: Descriptor) -> Descriptor.Output { switch descriptor { case let d as ViewDescriptor.RecipeDetailsDescriptor : return RecipeDetailsViewBuilder.build(with: d, environment: self) as! Descriptor.Outpu t } } } Cookpad શέʔε͕໢ཏ͞ΕΔΑ͏ʹ ຖճ࣮૷͠ͳͯ͘ྑ͍ ఆܗ֎ͷ࣮૷͕ෆՄೳʹ
  23. class MockRecipeSearchDataSource: RecipeSearchDataSource { lazy var searchRecipeReturnValue: Single<[Recipe]> = {

    fatalError("\ (#line)") }( ) // MARK: - searchRecipe var searchRecipeCallsCount = 0 var searchRecipeCalled: Bool { return buildCallsCount > 0 } func searchRecipe(keyword: String) -> Single<[Recipe]> { searchRecipeCallsCount += 1 return searchRecipeReturnValu e } } let dataStore = MockRecipeSearchDataSource( ) dataStore.searchRecipeReturnValue = .just([recipe0, recipe1] ) dataStore.searchRecipe(keyword: “φΠεΫϦʔϜ” )
  24. • 5/26 Cookpad Lounge #3 ʮΫοΫύου iOS ΞϓϦΛര଎Ͱ։ൃͰ ͖ΔΑ͏ʹ͢Δ࿩ʯ •

    https://www.youtube.com/watch?v=hqWiOeRyZ94 • 6/16 iOS Tech Talk ʙ Multi module ઓུ࠲ஊձ ʙ • https://www.youtube.com/watch?v=5p6h5yiQ2PQ • 7/2 iOS Tech Talk ʙ Multi module ઓུ࠲ஊձ vol.2 • https://www.youtube.com/watch?v=glpfnnDDaz8