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. Swinject ͱ DIKit Ͱ
    Dependency Injectionͯ͠Έͨ
    by.
    2018/4/3 ROPPONGI.swift ୈ2ճ
    1

    View Slide

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

    View Slide

  3. Dependency Injection
    ґଘੑ஫ೖ
    3

    View Slide

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

    View Slide

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

    View Slide

  6. 6

    View Slide


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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  12. DateRepositoryProtocol
    ґଘੑ
    ͸֎෦͔Β஫ೖ͠Α͏
    12

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  18. protocol Clock {
    var now: Date { get }
    }
    struct SystemClock: Clock {
    var now: Date { return Date() }
    }
    struct MockClock: Clock {
    let now: Date
    }
    18

    View Slide

  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

    View Slide

  20. ґଘੑ஫ೖ
    (Dependency Injection)
    ඞཁͳ΋ͷΛ
    ֎͔Β౉͢͜ͱ
    ͦΜ͚ͩ
    20

    View Slide

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

    View Slide

  22. ͜͏͍͏͜ͱ΍Γ͍ͨ
    struct SomeClock: Clock {
    var now: Date { ... }
    }
    // clock ΛҾ਺ʹ౉ͤ͹
    // ΠΠײ͡ʹViewController͕ಘΒΕΔ
    let vc = ViewController(
    clock: SomeClock()
    )
    self.pushViewController(vc, animated: true)
    22

    View Slide

  23. Ͱ΋…ʁ
    23

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  28. ClockΛ࡞ͬͨΒ
    DateRepositoryͱҰॹʹ
    UseCaseʹDIͯ͠
    ͦΕΛPresenterʹDIͯ͠
    ͦΕΛViewControllerʹDI
    28

    View Slide

  29. Dependency Graph
    29

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  33. Swinject
    33

    View Slide

  34. Swinject
    • Yoichi Tagaya͞Μ࡞
    • ଟػೳͳDIίϯςφ
    •Initialization Callback
    •Object Scopes
    •Container Hierarchy
    •etc
    • Extension΋ॆ࣮
    •SwinjectPropertyLoader
    •SwinjectStoryboard
    •Swinject-CodeGen
    •SwinjectAutoregistration
    34

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  39. Swinject ͷ Pros / Cons
    Pros
    • ͱʹ͔͘ଟػೳ
    • ༷ʑͳΠϯδΣΫγϣϯͷύλʔϯ΍ɺάϥϑղܾ࣌ͷࡉ΍͔ͳཁ๬ʹରԠ
    Cons
    • ྑ͘΋ѱ͘΋ɺΫϩʔδϟΛઃఆ͢ΔಈతελΠϧ
    • IUO(!)͕සൃ͠ɺܕ҆શͰ͸ͳ͍
    • ౉͢΂͖argumentsͳͲΛ஌Ζ͏ͱͯ͠΋ิ׬͕ར༻Ͱ͖ͳ͍
    • ࢖͍͜ͳͨ͢Ίͷֶशίετ
    39

    View Slide

  40. ٕज़ॻయ4 ͸
    4/22(೔)
    @ळ༿ݪUDX!
    40

    View Slide

  41. DIKit
    41

    View Slide

  42. DIKit
    • ishkawa ͞Μ࡞
    • iOSDCͰʮίʔυੜ੒ʹΑΔ੩తͳ
    Dependency Injectionʯʹ͍ͭͯ
    ࿩ͨ͠ & ޱ಄ݪߘΛެ։
    https://blog.ishkawa.org/
    2017/09/18/1505701774/
    42

    View Slide

  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

    View Slide

  44. With DIKit
    let clock = SystemClock()
    let vc = appResolver.resolveViewController(clock: clock)
    44

    View Slide

  45. ґଘάϥϑΛղܾͯ͘͠ΕΔ Resolver ͱ͍
    ͏ଘࡏΛߟ͑Δ
    !
    ͷதͰɺDependency͸3छྨʹ෼ྨՄ
    1. Ҿ਺ͱͯ͠౉͍ͨ͠΍ͭ
    •Clock
    2. Resolver ͕άϥϑ຤୺Ͱੜ੒͢Δ΍ͭ
    •DateRepositoryProtocol
    3. 2͕͋Ε͹҉໧తʹղܾͰ͖Δ΍ͭ
    •UseCase, Presenter, ViewController
    2͸ϓϩάϥϚͷखͰॻ͘ͱͯ͠ɺ
    3͸πʔϧʹΑΔࣗಈੜ੒Ͱ·͔ͳ͑Δʂɹ
    ͱ͍͏ൃ૝
    45

    View Slide

  46. 1. Resolver Λ༻ҙ͠ɺ
    Resolver ʹղܾ͍ͤͨ͞Dependency Λఆٛɾ࣮૷
    protocol AppResolver: Resolver {
    func provideDateRepository() -> DateRepositoryProtocol
    }
    final class AppResolverImpl: AppResolver {
    func provideDateRepository() -> DateRepositoryProtocol {
    return DateRepositoryImpl()
    }
    }
    46

    View Slide

  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

    View Slide

  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

    View Slide

  49. ͜ΕͰάϥϑղܾͷͨΊͷࡐྉ͸ἧͬͨ
    • Resolver ʹղܾ͍ͤͨ͞Dependency͸ Resolver ʹ࣮૷ࡁ
    • Dependency graph͸ DIKitͰఆٛࡁΈͷϓϩτίϧ
    ( Injectable ͳͲ)Ͱറ͍ͬͯΔ
    ͱ͍͏Θ͚Ͱ
    εΫϦϓτ dikitgen Λ૸ΒͤΔͱ…
    ʢRun scriptʹॻ͍ͱ͘ͷ΋͍͍͔΋ʣ
    dikitgen $SRCROOT/$PROJECT --exclude Carthage > $SRCROOT/$PROJECT/AppResolver.generated.swift
    49

    View Slide

  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

    View Slide

  51. DIKit ͷ Pros / Cons
    Pros
    • ܕ҆શ
    • ॻ͘ίʔυྔ͕ѹ౗తʹগͳ͍
    • ࣗಈੜ੒ϕʔεͳͷͰมߋʹ΋ڧ͍
    Cons
    • είʔϓ͕ͳ͍
    • δΣωϨʔλͷ଎౓໰୊
    • DI͕ඞཁͳܕ͕ɺInjectable ϓϩτίϧʹറΒΕΔ
    • ຊ౰ʹCons? ݩʑͦ͏͋Δ΂͖ͱ͍͏ߟ͑΋ʁ
    51

    View Slide

  52. Cake Pattern
    52

    View Slide

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

    View Slide

  54. Dependency Injection ͷܗࣜ
    • ίϯετϥΫλɾΠϯδΣΫγϣϯ
    • ηολʔɾΠϯδΣΫγϣϯ
    • ΠϯλϑΣʔεɾΠϯδΣΫγϣϯ
    • Mix-in ΠϯδΣΫγϣϯ
    !
    Cake Pattern͸͜Ε
    54

    View Slide

  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

    View Slide

  56. ͦͷͨΊࠓճͷαϯϓϧΞϓϦ΁ͷద༻͸೉͍͠…͚ΕͲ
    Cake Pattern Λ࢖͓͏ͱͯ͠ग़དྷ্͕ͬͨ
    Cake Patternʹ
    ࣅͯඇͳΔԿ͔
    ΋࣮૷ͯ͠͸Έ·ͨ͠ɻ
    56

    View Slide

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

    View Slide