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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for giginet giginet PRO
September 19, 2021

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

Avatar for giginet

giginet PRO

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