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

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

704056da9a4c4e075ad14479beaebab7?s=47 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

704056da9a4c4e075ad14479beaebab7?s=128

takasek

April 03, 2018
Tweet

Transcript

  1. Swinject ͱ DIKit Ͱ Dependency Injectionͯ͠Έͨ by. 2018/4/3 ROPPONGI.swift ୈ2ճ

    1
  2. takasek iOS Developer @takasek OSS ActionClosurable౳ Xcode Extension PasteTheType 2

  3. Dependency Injection ґଘੑ஫ೖ 3

  4. ڧͦ͏ͳ໊લ͚ͩͲ…ͭ·Γ͸ ඞཁͳ΋ͷΛ ֎͔Β౉͢͜ͱ1 1 iOSDCͰʮίʔυੜ੒ʹΑΔ੩తͳDependency Injectionʯʹ͍ͭͯ࿩ͨ͠ & ޱ಄ݪߘΛެ։ https://blog.ishkawa.org/2017/09/18/1505701774/ 4

  5. αϯϓϧΞϓϦ • લճΞΫηε࣌ࠁͱ ݱࡏ࣌ࠁΛදࣔ • લճΞΫηε࣌ࠁ͸ UserDefaultsͰอଘ 5

  6. 6

  7. ⚠ UserDefaults (۩৅)΁ͷґଘ 7

  8. UserDefaults΁ͷґଘΛ ந৅΁ͷґଘ ΁ͱॻ͖ସ͑Α͏ 8

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

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

    DateRepositoryProtocol = DateRepositoryImpl() func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) ... } ... } 10
  11. 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
  12. DateRepositoryProtocol ґଘੑ ͸֎෦͔Β஫ೖ͠Α͏ 12

  13. final class UseCase { ... let dateRepository: DateRepositoryProtocol = DateRepositoryImpl()

    // ܾΊଧͪͰ࢖͏ͷͰ͸ͳ͘… func load() { let lastDate = dateRepository.fetchLastDate() item = Item(lastDate: lastDate, now: Date()) ... } ... } 13
  14. 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
  15. 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
  16. 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
  17. 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
  18. protocol Clock { var now: Date { get } }

    struct SystemClock: Clock { var now: Date { return Date() } } struct MockClock: Clock { let now: Date } 18
  19. 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
  20. ґଘੑ஫ೖ (Dependency Injection) ඞཁͳ΋ͷΛ ֎͔Β౉͢͜ͱ ͦΜ͚ͩ 20

  21. ෭࣍తޮՌ ͓ΊͰͱ͏ʂ ʮݱࡏ࣌ࠁΛදࣔ͢Δը໘ʯ͸ ʮ೚ҙͷ࣌ࠁΛදࣔ͢Δը໘ʯʹ ਐԽͨ͠ʂ 21

  22. ͜͏͍͏͜ͱ΍Γ͍ͨ struct SomeClock: Clock { var now: Date { ...

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

  24. Ͱ΋…ʁ final class UseCase { ... let dateRepository: DateRepositoryProtocol let

    clock: Clock init(dependency: Dependency) { self.dateRepository = dependency.dateRepository self.clock = dependency.clock } ... } 24
  25. Ͱ΋…ʁ 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
  26. Ͱ΋…ʁ 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
  27. ݱ࣮͸ ͜͏Ͱ͢ 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
  28. ClockΛ࡞ͬͨΒ DateRepositoryͱҰॹʹ UseCaseʹDIͯ͠ ͦΕΛPresenterʹDIͯ͠ ͦΕΛViewControllerʹDI 28

  29. Dependency Graph 29

  30. ຊ౰ʹ౉͍ͨ͠ͷ͸ Clock͚ͩ 30

  31. άϥϑͷղܾΛ Ҿ͖ड͚ΔͨΊʹ DI framework͕͋Δ 31

  32. 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
  33. Swinject 33

  34. Swinject • Yoichi Tagaya͞Μ࡞ • ଟػೳͳDIίϯςφ •Initialization Callback •Object Scopes

    •Container Hierarchy •etc • Extension΋ॆ࣮ •SwinjectPropertyLoader •SwinjectStoryboard •Swinject-CodeGen •SwinjectAutoregistration 34
  35. 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
  36. 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
  37. 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
  38. 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
  39. Swinject ͷ Pros / Cons Pros • ͱʹ͔͘ଟػೳ • ༷ʑͳΠϯδΣΫγϣϯͷύλʔϯ΍ɺάϥϑղܾ࣌ͷࡉ΍͔ͳཁ๬ʹରԠ

    Cons • ྑ͘΋ѱ͘΋ɺΫϩʔδϟΛઃఆ͢ΔಈతελΠϧ • IUO(!)͕සൃ͠ɺܕ҆શͰ͸ͳ͍ • ౉͢΂͖argumentsͳͲΛ஌Ζ͏ͱͯ͠΋ิ׬͕ར༻Ͱ͖ͳ͍ • ࢖͍͜ͳͨ͢Ίͷֶशίετ 39
  40. ٕज़ॻయ4 ͸ 4/22(೔) @ळ༿ݪUDX! 40

  41. DIKit 41

  42. DIKit • ishkawa ͞Μ࡞ • iOSDCͰʮίʔυੜ੒ʹΑΔ੩తͳ Dependency Injectionʯʹ͍ͭͯ ࿩ͨ͠ &

    ޱ಄ݪߘΛެ։ https://blog.ishkawa.org/ 2017/09/18/1505701774/ 42
  43. 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
  44. With DIKit let clock = SystemClock() let vc = appResolver.resolveViewController(clock:

    clock) 44
  45. ґଘάϥϑΛղܾͯ͘͠ΕΔ Resolver ͱ͍ ͏ଘࡏΛߟ͑Δ ! ͷதͰɺDependency͸3छྨʹ෼ྨՄ 1. Ҿ਺ͱͯ͠౉͍ͨ͠΍ͭ •Clock 2.

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

    func provideDateRepository() -> DateRepositoryProtocol } final class AppResolverImpl: AppResolver { func provideDateRepository() -> DateRepositoryProtocol { return DateRepositoryImpl() } } 46
  47. 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
  48. 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
  49. ͜ΕͰάϥϑղܾͷͨΊͷࡐྉ͸ἧͬͨ • Resolver ʹղܾ͍ͤͨ͞Dependency͸ Resolver ʹ࣮૷ࡁ • Dependency graph͸ DIKitͰఆٛࡁΈͷϓϩτίϧ

    ( Injectable ͳͲ)Ͱറ͍ͬͯΔ ͱ͍͏Θ͚Ͱ εΫϦϓτ dikitgen Λ૸ΒͤΔͱ… ʢRun scriptʹॻ͍ͱ͘ͷ΋͍͍͔΋ʣ dikitgen $SRCROOT/$PROJECT --exclude Carthage > $SRCROOT/$PROJECT/AppResolver.generated.swift 49
  50. 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
  51. DIKit ͷ Pros / Cons Pros • ܕ҆શ • ॻ͘ίʔυྔ͕ѹ౗తʹগͳ͍

    • ࣗಈੜ੒ϕʔεͳͷͰมߋʹ΋ڧ͍ Cons • είʔϓ͕ͳ͍ • δΣωϨʔλͷ଎౓໰୊ • DI͕ඞཁͳܕ͕ɺInjectable ϓϩτίϧʹറΒΕΔ • ຊ౰ʹCons? ݩʑͦ͏͋Δ΂͖ͱ͍͏ߟ͑΋ʁ 51
  52. Cake Pattern 52

  53. Dependency Injection ͷܗࣜ2 • ίϯετϥΫλɾΠϯδΣΫγϣϯ • ηολʔɾΠϯδΣΫγϣϯ • ΠϯλϑΣʔεɾΠϯδΣΫγϣϯ Cake

    Pattern ͸ͲΕͰ͠ΐ͏ 2 Inversion of Control ίϯςφͱ Dependency Injection ύλʔϯ http://kakutani.com/trans/fowler/injection.html#FormsOfDependencyInjection 53
  54. Dependency Injection ͷܗࣜ • ίϯετϥΫλɾΠϯδΣΫγϣϯ • ηολʔɾΠϯδΣΫγϣϯ • ΠϯλϑΣʔεɾΠϯδΣΫγϣϯ •

    Mix-in ΠϯδΣΫγϣϯ ! Cake Pattern͸͜Ε 54
  55. 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
  56. ͦͷͨΊࠓճͷαϯϓϧΞϓϦ΁ͷద༻͸೉͍͠…͚ΕͲ Cake Pattern Λ࢖͓͏ͱͯ͠ग़དྷ্͕ͬͨ Cake Patternʹ ࣅͯඇͳΔԿ͔ ΋࣮૷ͯ͠͸Έ·ͨ͠ɻ 56

  57. ࠓճͷ֤DI frameworkͷ ࣮૷αϯϓϧ͸ͪ͜Β ʢςετίʔυ΋͋Γ·͢ʣ https://github.com/takasek/ DependencyInjectionSample 57