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

Swinject と DIKit で Dependency Injection してみた / 20180403 #roppongiswift

takasek
April 03, 2018

Swinject と DIKit で Dependency Injection してみた / 20180403 #roppongiswift

ROPPONGI.swift 第2回 - connpass
https://visits.connpass.com/event/82783/
での発表資料です。

# サンプルアプリリポジトリ

(DIの実装パターンに加え、テストコードもあります)
https://github.com/takasek/DependencyInjectionSample

# 参考リンク

Swinject/Swinject: Dependency injection framework for Swift with iOS/macOS/Linux
https://github.com/Swinject/Swinject

ishkawa/DIKit: A statically typed dependency injector for Swift.
https://github.com/ishkawa/DIKit/

iOSDCで「コード生成による静的なDependency Injection」について話した & 口頭原稿を公開
https://blog.ishkawa.org/2017/09/18/1505701774/

DI with "Reader Monad" // Speaker Deck
https://speakerdeck.com/to4iki/di-with-reader-monad

Scalaにおける最適なDependency Injectionの方法を考察する 〜なぜドワンゴアカウントシステムの生産性は高いのか〜 - Qiita
https://qiita.com/pab_tech/items/1c0bdbc8a61949891f1f

Inversion of Control コンテナと Dependency Injection パターン
http://kakutani.com/trans/fowler/injection.html#FormsOfDependencyInjection

翻訳: "Cake Pattern: The Bakery from the Black Lagoon" - Okapies' Archive
http://okapies.hateblo.jp/entry/2013/07/15/232456

takasek

April 03, 2018
Tweet

More Decks by takasek

Other Decks in Technology

Transcript

  1. 6

  2. final class UseCase { ... // ! ۩৅Λ let dateRepository

    = DateRepository() func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) ... } ... } 9
  3. final class UseCase { ... // ! ந৅ʹ let dateRepository:

    DateRepositoryProtocol = DateRepositoryImpl() func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) ... } ... } 10
  4. protocol DateRepositoryProtocol { func fetchLastDate() -> Date? mutating func saveCurrentDate(_

    now: Date) } final class DateRepositoryImpl: DateRepositoryProtocol { func fetchLastDate() -> Date? { // UserDefaults ͔Βऔಘ ... } func saveCurrentDate(_ now: Date) { // UserDefaults ʹอଘ ... } } struct MockDateRepositoryImpl: DateRepositoryProtocol { var lastDate: Date? func fetchLastDate() -> Date? { return lastDate } mutating func saveCurrentDate(_ now: Date) { lastDate = now } } 11
  5. final class UseCase { ... let dateRepository: DateRepositoryProtocol = DateRepositoryImpl()

    // ܾΊଧͪͰ࢖͏ͷͰ͸ͳ͘… func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) ... } ... } 13
  6. final class UseCase { ... let dateRepository: DateRepositoryProtocol // !

    ඞࡴʂɹίϯετϥΫλɾΠϯδΣΫγϣϯʂ init(dateRepository: DateRepositoryProtocol) { self.dateRepository = dateRepository } func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) ... } ... } 14
  7. final class UseCase { ... let dateRepository: DateRepositoryProtocol // ʘ͜ΕͰ׬੒ͩͱࢥͬͨʁʗ

    init(dateRepository: DateRepositoryProtocol) { self.dateRepository = dateRepository } func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) ... } ... } 15
  8. final class UseCase { ... let dateRepository: DateRepositoryProtocol // ʘґଘੑ͸Զ͚ͩ͡Όͳ͍ͥʗ

    init(dateRepository: DateRepositoryProtocol) { self.dateRepository = dateRepository } func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) ... } ... } 16
  9. final class UseCase { ... let dateRepository: DateRepositoryProtocol // ʘґଘੑ͸Զ͚ͩ͡Όͳ͍ͥʗ

    init(dateRepository: DateRepositoryProtocol) { self.dateRepository = dateRepository } func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) // ͋ͬ ... } ... } 17
  10. protocol Clock { var now: Date { get } }

    struct SystemClock: Clock { var now: Date { return Date() } } struct MockClock: Clock { let now: Date } 18
  11. final class UseCase { ... let dateRepository: DateRepositoryProtocol let clock:

    Clock // ! init(dependency: Dependency) { self.dateRepository = dependency.dateRepository self.clock = dependency.clock // ! } func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: clock.now()) // ... } ... } 19
  12. ͜͏͍͏͜ͱ΍Γ͍ͨ struct SomeClock: Clock { var now: Date { ...

    } } // clock ΛҾ਺ʹ౉ͤ͹ // ΠΠײ͡ʹViewController͕ಘΒΕΔ let vc = ViewController( clock: SomeClock() ) self.pushViewController(vc, animated: true) 22
  13. Ͱ΋…ʁ final class UseCase { ... let dateRepository: DateRepositoryProtocol let

    clock: Clock init(dependency: Dependency) { self.dateRepository = dependency.dateRepository self.clock = dependency.clock } ... } 24
  14. Ͱ΋…ʁ final class UseCase { ... let dateRepository: DateRepositoryProtocol let

    clock: Clock init(dependency: Dependency) { self.dateRepository = dependency.dateRepository self.clock = dependency.clock } ... } final class Presenter { let useCase: UseCase init(dependency: Dependency) { useCase = dependency.useCase ... } } 25
  15. Ͱ΋…ʁ final class UseCase { ... let dateRepository: DateRepositoryProtocol let

    clock: Clock init(dependency: Dependency) { self.dateRepository = dependency.dateRepository self.clock = dependency.clock } ... } final class Presenter { let useCase: UseCase init(dependency: Dependency) { useCase = dependency.useCase ... } } final class ViewController: UIViewController { ... var presenter: Presenter! ... } 26
  16. ݱ࣮͸ ͜͏Ͱ͢ let repository = DateRepositoryImpl() let clock = SystemClock()

    let useCase = UseCase(dependency: .init( dateRepository: repository, clock: clock )) let presenter = Presenter(dependency: .init( useCase: useCase )) let vc = UIStoryBoard(...).instantiate() vc.presenter = presenter 27
  17. DI framework ಈతDI framework (ϥϯλΠϜͰͷղܾ) • DIίϯςφʢSwinject, Cleanseʣ ੩తDI framework

    (ίϯύΠϥʹΑΔղܾ) • DIKitʢίʔυੜ੒Λ׆༻ʣ • Cake PatternʢProtocol ExtensionͰmix-inʣ • https://qiita.com/pab_tech/items/1c0bdbc8a61949891f1f • Reader Monad • https://speakerdeck.com/to4iki/di-with-reader-monad 32
  18. Swinject • Yoichi Tagaya͞Μ࡞ • ଟػೳͳDIίϯςφ •Initialization Callback •Object Scopes

    •Container Hierarchy •etc • Extension΋ॆ࣮ •SwinjectPropertyLoader •SwinjectStoryboard •Swinject-CodeGen •SwinjectAutoregistration 34
  19. Without Swinject let repository = DateRepositoryImpl() let clock = SystemClock()

    let useCase = UseCase(dependency: .init( dateRepository: repository, clock: clock )) let presenter = Presenter(dependency: .init( useCase: useCase )) let vc = UIStoryBoard(...).instantiate() vc.presenter = presenter 35
  20. With Swinject only let clock = SystemClock() as Clock let

    vc = container.resolve(ViewController.self, argument: clock) With Swinject & SwinjectStoryboard let vc = SwinjectStoryboard .create(name: "...", bundle: nil) .instantiateViewController(withIdentifier: "...") 36
  21. Swinjectͷ࢖͍ํ // جຊߏ଄: ܕ͝ͱʹΫϩʔδϟΛregister͢Δ ! ͦΕΛresolveͰݺͿ defaultContainer.register(DateRepositoryProtocol.self) { _ in

    DateRepositoryImpl() } // ΫϩʔδϟͷୈೋҾ਺Ҏ߱Ͱɺresolve࣌ʹ࢖͏Ҿ਺Λࢦఆ͢Δ͜ͱ΋Ͱ͖Δ defaultContainer.register(UseCase.self) { (r: Resolver, clock: Clock) in UseCase(dependency: .init( dateRepository: r.resolve(DateRepositoryProtocol.self)!, // άϥϑͷղܾ clock: clock )) } defaultContainer.register(Presenter.self) { (r: Resolver, clock: Clock) in Presenter(dependency: .init( useCase: r.resolve(UseCase.self, argument: clock)! )) } 37
  22. Swinject + SwinjectStoryboard ͷ࢖͍ํ // ܕʹՃ͑ͯ name Λࢦఆͯ͠register΋Ͱ͖Δ defaultContainer.register(Clock.self, name:

    "ViewController") { _ in return SystemClock() } // SwinjectStoryboard ͷ͍͢͝ͱ͜Ζ // StoryboardͰVC͕࡞ΒΕͨλΠϛϯάͰϑοΫͯ͠ // ϓϩύςΟɾΠϯδΣΫγϣϯͰ͖Δ defaultContainer.storyboardInitCompleted(ViewController.self) { r, vc in let clock = r.resolve(Clock.self, name: "ViewController")! vc.presenter = r.resolve(Presenter.self, argument: clock)! } 38
  23. Swinject ͷ Pros / Cons Pros • ͱʹ͔͘ଟػೳ • ༷ʑͳΠϯδΣΫγϣϯͷύλʔϯ΍ɺάϥϑղܾ࣌ͷࡉ΍͔ͳཁ๬ʹରԠ

    Cons • ྑ͘΋ѱ͘΋ɺΫϩʔδϟΛઃఆ͢ΔಈతελΠϧ • IUO(!)͕සൃ͠ɺܕ҆શͰ͸ͳ͍ • ౉͢΂͖argumentsͳͲΛ஌Ζ͏ͱͯ͠΋ิ׬͕ར༻Ͱ͖ͳ͍ • ࢖͍͜ͳͨ͢Ίͷֶशίετ 39
  24. Without DIKit let repository = DateRepositoryImpl() let clock = SystemClock()

    let useCase = UseCase(dependency: .init( dateRepository: repository, clock: clock )) let presenter = Presenter(dependency: .init( useCase: useCase )) let vc = ViewController.makeInstance(dependency: init( presenter: presenter )) 43
  25. ґଘάϥϑΛղܾͯ͘͠ΕΔ Resolver ͱ͍ ͏ଘࡏΛߟ͑Δ ! ͷதͰɺDependency͸3छྨʹ෼ྨՄ 1. Ҿ਺ͱͯ͠౉͍ͨ͠΍ͭ •Clock 2.

    Resolver ͕άϥϑ຤୺Ͱੜ੒͢Δ΍ͭ •DateRepositoryProtocol 3. 2͕͋Ε͹҉໧తʹղܾͰ͖Δ΍ͭ •UseCase, Presenter, ViewController 2͸ϓϩάϥϚͷखͰॻ͘ͱͯ͠ɺ 3͸πʔϧʹΑΔࣗಈੜ੒Ͱ·͔ͳ͑Δʂɹ ͱ͍͏ൃ૝ 45
  26. 1. Resolver Λ༻ҙ͠ɺ Resolver ʹղܾ͍ͤͨ͞Dependency Λఆٛɾ࣮૷ protocol AppResolver: Resolver {

    func provideDateRepository() -> DateRepositoryProtocol } final class AppResolverImpl: AppResolver { func provideDateRepository() -> DateRepositoryProtocol { return DateRepositoryImpl() } } 46
  27. 2. ҉໧తʹղܾͰ͖ΔDependency͸… final class ViewController: UIViewController, PresenterDelegate { struct Dependency

    { let presenter: Presenter } static func makeInstance(dependency: Dependency) -> ViewController { let vc = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! ViewController vc.presenter = dependency.presenter return vc } var presenter: Presenter! ... } final class Presenter: UseCaseDelegate { struct Dependency { let useCase: UseCase } let useCase: UseCase init(dependency: Dependency) { useCase = dependency.useCase ... } } 47
  28. 2. ҉໧తʹղܾͰ͖ΔDependency͸ϓϩτίϧͰറΔ final class ViewController: UIViewController, PresenterDelegate, FactoryMethodInjectable { struct

    Dependency { let presenter: Presenter } static func makeInstance(dependency: Dependency) -> ViewController { let vc = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! ViewController vc.presenter = dependency.presenter return vc } var presenter: Presenter! ... } final class Presenter: UseCaseDelegate, Injectable { struct Dependency { let useCase: UseCase } let useCase: UseCase init(dependency: Dependency) { useCase = dependency.useCase ... } } 48
  29. ͜ΕͰάϥϑղܾͷͨΊͷࡐྉ͸ἧͬͨ • Resolver ʹղܾ͍ͤͨ͞Dependency͸ Resolver ʹ࣮૷ࡁ • Dependency graph͸ DIKitͰఆٛࡁΈͷϓϩτίϧ

    ( Injectable ͳͲ)Ͱറ͍ͬͯΔ ͱ͍͏Θ͚Ͱ εΫϦϓτ dikitgen Λ૸ΒͤΔͱ… ʢRun scriptʹॻ͍ͱ͘ͷ΋͍͍͔΋ʣ dikitgen $SRCROOT/$PROJECT --exclude Carthage > $SRCROOT/$PROJECT/AppResolver.generated.swift 49
  30. AppResolver.generated.swift ͕ࣗಈੜ੒͞ΕΔ extension AppResolver { func resolveDateRepositoryProtocol() -> DateRepositoryProtocol {

    return provideDateRepository() } func resolveUseCase(clock: Clock) -> UseCase { let dateRepositoryProtocol = resolveDateRepositoryProtocol() return UseCase(dependency: .init(dateRepository: dateRepositoryProtocol, clock: clock)) } func resolvePresenter(clock: Clock) -> Presenter { let useCase = resolveUseCase(clock: clock) return Presenter(dependency: .init(useCase: useCase)) } func resolveViewController(clock: Clock) -> ViewController { let presenter = resolvePresenter(clock: clock) return ViewController.makeInstance(dependency: .init(presenter: presenter)) } } 50
  31. DIKit ͷ Pros / Cons Pros • ܕ҆શ • ॻ͘ίʔυྔ͕ѹ౗తʹগͳ͍

    • ࣗಈੜ੒ϕʔεͳͷͰมߋʹ΋ڧ͍ Cons • είʔϓ͕ͳ͍ • δΣωϨʔλͷ଎౓໰୊ • DI͕ඞཁͳܕ͕ɺInjectable ϓϩτίϧʹറΒΕΔ • ຊ౰ʹCons? ݩʑͦ͏͋Δ΂͖ͱ͍͏ߟ͑΋ʁ 51
  32. Dependency Injection ͷܗࣜ2 • ίϯετϥΫλɾΠϯδΣΫγϣϯ • ηολʔɾΠϯδΣΫγϣϯ • ΠϯλϑΣʔεɾΠϯδΣΫγϣϯ Cake

    Pattern ͸ͲΕͰ͠ΐ͏ 2 Inversion of Control ίϯςφͱ Dependency Injection ύλʔϯ http://kakutani.com/trans/fowler/injection.html#FormsOfDependencyInjection 53
  33. Cake Pattern ͢΂ͯΛprotocol extensionʹد͍͖ͤͯɺ ґଘੑ͸protocolͰఆٛ͞ΕͨϓϩύςΟ͔Βऔಘ …ͱ͍͏ߟ͑ํ͕جຊʹ͋Δ3 3 ຋༁: "Cake Pattern:

    The Bakery from the Black Lagoon" - Okapies' Archive http://okapies.hateblo.jp/entry/2013/07/15/232456 55