$30 off During Our Annual Pro Sale. View Details »

Manual DI with ReactorKit

masakazu sano
September 20, 2018
570

Manual DI with ReactorKit

masakazu sano

September 20, 2018
Tweet

Transcript

  1. iOSDC 2018 Reject Conference days2
    Manual DI
    With ReactorKit
    20.Oct.2018 Masakazu sano

    View Slide

  2. Masakazu Sano
    (@kz56cd)
    - iOS Developer
    - Freelance
    - like:
    -
    -
    -

    View Slide

  3. Masakazu Sano
    (@kz56cd)
    - iOS Developer
    - Freelance
    - like:
    - snowboard …
    - DbD (ps4) …
    - ଍ཪϚοαʔδ …

    View Slide

  4. "

    View Slide

  5. 01:
    ಋೖ

    View Slide

  6. View Slide

  7. DIɺ࢖ͬͯ·͔͢ʁ

    View Slide

  8. ґଘੑͷ஫ೖʢ͍ͦΜ͍ͤͷͪΎ͏ʹΎ͏ɺ
    ӳ: Dependency injectionʣͱ͸ɺ
    ίϯϙʔωϯτؒͷґଘؔ܎ΛϓϩάϥϜͷ
    ιʔείʔυ͔Βഉআ͠ɺ֎෦ͷઃఆϑΝΠ
    ϧͳͲͰ஫ೖͰ͖ΔΑ͏ʹ͢Διϑτ΢ΣΞ
    ύλʔϯͰ͋Δɻӳޠͷ಄จࣈ͔ΒDIͱུ͞
    ΕΔɻ
    ґଘੑͷ஫ೖ - Wikipedia

    View Slide

  9. iOSΞϓϦʹ͓͚Δ
    Manual DIͱ͸

    View Slide

  10. iOSΞϓϦʹ͓͚ΔManual DIͱ͸
    • Πϯελϯεੜ੒ͷ৔ॴ / λΠϛϯά͕੔ཧͰ͖ɺ
    ҆৺ײ͕͋Δ
    • segueΛ࢖Θͳͯ͘ࡁΉ
    • ςετ͠΍͘͢ͳΔ (DI΋Viewଆ΋)
    • ಋೖ΋؆୯

    View Slide

  11. ಋೖͨ͠ಈػʹ͍ͭͯ

    View Slide

  12. ʢׂѪʣ

    View Slide

  13. DIͷجຊ֓೦

    View Slide

  14. DIͷجຊ֓೦
    • ૊Έ߹Θͤͯ࡞Δ
    • Coordinator pattern
    • Factory pattern

    View Slide

  15. App Architecture by Manual DI (@yoshikuni_kato)

    View Slide

  16. Coordinatorύλʔϯͷ࣮ફ (@yoshikuni_kato)

    View Slide

  17. 02:
    ΍ͬͯΈΔ (DIฤ)

    View Slide

  18. DIͷߏ੒ʹ͍ͭͯ

    View Slide

  19. View Slide

  20. ֤DIύʔπͷ੹຿ʹ͍ͭͯ

    View Slide

  21. View Slide

  22. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    ViewControllerFactory
    • ֤ViewController / Reactor ΛΠχγϟϥΠζɺฦ͢
    • ComponentsΛอ࣋
    • ΠχγϟϥΠζ࣌ʹඞཁͳServiceΛηοτ͢Δ

    View Slide

  23. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    ViewControllerFactory
    func favoriteTop() -> FavoritesViewController {
    let viewController =
    StoryboardScene.FavoritesViewController.initialScene.instantiate()
    viewController.reactor = FavoritesViewReactor(
    favoriteService: components.favoriteService,
    userDefaultsService: components.userDefaultsService,
    errorHandler: components.errorHandler
    )
    return viewController
    }
    protocol ViewControllerFactoryType {
    func favoriteTop() -> FavoritesViewController
    }

    View Slide

  24. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    CoordinatorFactory
    • ֤Coordinator ΛΠχγϟϥΠζ͠ɺฦ͢
    • ComponentsɺViewControllerFactoryΛอ࣋
    • ʢViewControllerFactoryͱࣅͨߏ੒ʣ

    View Slide

  25. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    CoordinatorFactory
    func favoriteCoordinator() -> FavoriteCoordinator {
    return FavoriteCoordinator(
    viewControllerFactory: viewControllerFactory
    )
    }
    protocol CoordinatorFactoryType {
    func favoriteCoordinator() -> FavoriteCoordinator
    }

    View Slide

  26. View Slide

  27. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    Components
    • ֤ServiceΛอ࣋

    View Slide

  28. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    Service
    • ΞϓϦͷ֤ػೳΛ੾Γग़ͨ͠΋ͷ
    • ʮ͓ؾʹೖΓʯʮࣸਅ౤ߘʯػೳͳͲ…
    • APIͱͷ΍ΓͱΓ΋ServiceΛܦ༝͢Δ

    View Slide

  29. View Slide

  30. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    Coordinator
    • ը໘άϧʔϓͷॳظը໘දࣔɺϧʔςΟϯάΛ୲͏
    • άϧʔϓ͸λϒ΍φϏήʔγϣϯຖʹ۠੾ΔͱΑ͍
    • ViewControllerFactory, ʢՕॴʹΑͬͯ͸ʣ
    CoordinatorFactoryΛอ࣋

    View Slide

  31. View Slide

  32. View Slide

  33. View Slide

  34. View Slide

  35. View Slide

  36. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    Coordinator
    protocol ViewControllerCoordinator {
    var presenter: UIViewController { get }
    func start()
    }

    View Slide

  37. Coordinator
    func start() {
    let viewController = viewControllerFactory.favoriteTop()
    navigationController.pushViewController(viewController,
    animated: true)
    _ = viewController.reactor?
    .routeSelected
    .subscribe(onNext: { [weak self] route in
    guard let self = self,
    let route = route else {
    return
    }
    switch route {
    case .photoDetail(let photoId):
    self.pushPhotoDetail(photoId: photoId)
    }
    })
    }

    View Slide

  38. ֤DIύʔπͷ੹຿ʹ͍ͭͯ
    Presentable
    ΞϓϦΛઃܭ͍ͯ͘͠ͱɺ
    ෳ਺ͷCoordinatorͰॏෳͯ͠ද͍ࣔͨ͠ը໘ ͕Ͱͯ͘Δ
    • ͦΕΒ͸Presentableܦ༝Ͱݺͼग़͢
    • ࢖͍͍ͨCoordinatorʹର͠ɺProtocol-Orientedʹͯର
    Ԡͤ͞Δ

    View Slide

  39. 03:
    ΍ͬͯΈΔ (ϧʔςΟϯάฤ)

    View Slide

  40. ͦͷલʹ

    View Slide

  41. ReactorKit

    View Slide

  42. View Slide

  43. View Slide

  44. View Slide

  45. Routing

    View Slide

  46. View Slide

  47. View Slide

  48. ViewController
    final class FavoritesViewController: UIViewController, StoryboardView {
    @IBOutlet weak var tableView: UITableView!
    var disposeBag = DisposeBag()
    override func viewDidLoad() {
    super.viewDidLoad()
    }
    }
    // MARK: - ReactorKit
    extension FavoritesViewController {
    func bind(reactor: FavoritesViewReactor) {
    // Action
    self.tableView.rx.itemSelected
    .map { Reactor.Action.tapFavoritePhoto(row: $0.row) }
    .bind(to: reactor.action)
    .disposed(by: disposeBag)
    // State
    }
    }

    View Slide

  49. protocol FavoritesViewRouting {
    var routeSelected: Observable { get }
    }
    enum FavoritesViewRouter {
    case photoDetail(photoId: Int)
    }
    extension FavoritesViewReactor: FavoritesViewRouting {
    }
    Reactor

    View Slide

  50. final class FavoritesViewReactor: Reactor {
    enum Action {
    case tapFavoritePhoto(row: Int)
    }
    enum Mutation {
    }
    struct State {
    var favoriteIds: [Int]
    }
    let routeSelected: Observable
    let initialState: State
    private let userDefaultsService: UserDefaultsService
    private var routeSelectedSubject =
    PublishSubject()
    init(userDefaultsService: UserDefaultsService) {
    self.userDefaultsService = userDefaultsService
    initialState = State(favoriteIds:
    userDefaultsService.getFavoriteIds())
    routeSelected = routeSelectedSubject.asObservable()
    }
    }

    View Slide

  51. final class FavoritesViewReactor: Reactor {
    func mutate(action: Action) -> Observable {
    switch action {
    case .tapFavoritePhoto(let row):
    routeSelectedSubject.onNext(
    .spaceDetail(photoId: currentState.favoriteIds[row]!)
    )
    return Observable.empty()
    }
    }
    func reduce(
    state: State,
    mutation: Mutation
    ) -> State {
    switch mutation {
    // no mutation cases
    }
    return state
    }
    }
    extension FavoritesViewReactor: FavoritesViewRouting {
    }

    View Slide

  52. DI(v1) ͷ׬੒ʂ

    View Slide

  53. View Slide

  54. View Slide

  55. ຊ౰ʹ͜ΕͰΑ͍ͷ͔ʁ

    View Slide

  56. 04:
    ߋʹൃలͤͯ͞ΈΔ

    View Slide

  57. ReactorKitΒ͍͠DIͱ͸

    View Slide

  58. ݱঢ়

    View Slide

  59. ʢຊ౰͸ͬͪ͜ͷํ͕ྑ͘ͳ͍ͩΖ͏͔ʣ

    View Slide

  60. ReactorKitΒ͍͠DIͱ͸
    վྑͷྲྀΕ
    1. Reactor ͷมߋ
    2. ViewController ͷมߋ
    3. ରԠ͢ΔCoordinator ͷมߋ (ϧʔςΟϯάՕॴ)

    View Slide

  61. 1. Reactor ͷมߋ
    final class FavoritesViewReactor: Reactor {
    enum Action {
    case actViewWillAppear
    case tapFavoritePhoto(row: Int)
    }
    enum Mutation {
    case setRoute(FavoriteViewRouter?)
    }
    struct State {
    var favoriteIds: [Int]
    }
    let routeSelected: Observable
    let initialState: State
    private let userDefaultsService: UserDefaultsService
    private var routeSelectedSubject =
    PublishSubject()
    // init͸লུ
    }
    Add
    Add

    View Slide

  62. 1. Reactor ͷมߋ
    final class FavoritesViewReactor: Reactor {
    enum Action {
    case actViewWillAppear
    case tapFavoritePhoto(row: Int)
    }
    enum Mutation {
    case setRoute(FavoriteViewRouter?)
    }
    struct State {
    var favoriteIds: [Int]
    }
    let routeSelected: Observable
    let initialState: State
    private let userDefaultsService: UserDefaultsService
    private var routeSelectedSubject =
    PublishSubject()
    // init͸লུ
    }
    Replace (into VC)
    Replace (into VC)

    View Slide

  63. func mutate(action: Action) -> Observable {
    switch action {
    case .actViewWillAppear:
    return Observable.just(.setRoute(nil))
    case .tapFavoritePhoto(let row):
    return Observable.just(
    .setRoute(
    .spaceDetail(
    photoId: currentState.favoriteIds[row]!
    )
    )
    )
    }
    }
    func reduce(
    state: State,
    mutation: Mutation
    ) -> State {
    var newState = state
    switch mutation {
    case .setRoute(let route):
    newState.route = route
    }
    return state
    }
    Add
    Add

    View Slide

  64. 2. ViewController ͷมߋ
    Add
    Add
    final class FavoritesViewController: UIViewController,
    StoryboardView {
    @IBOutlet weak var tableView: UITableView!
    private let viewWillAppearSubject = PublishSubject()
    private var routeSelectedSubject =
    PublishSubject()
    var disposeBag = DisposeBag()
    override func viewDidLoad() {
    super.viewDidLoad()
    }
    override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    viewWillAppearSubject.onNext(())
    }
    }
    extension FavoritesViewController: FavoritesViewRouting {
    var routeSelected: Observable {
    return routeSelectedSubject.asObservable()
    }
    }
    Replace (by Reactor)

    View Slide

  65. 2. ViewController ͷมߋ
    Add
    Add
    final class FavoritesViewController: UIViewController,
    StoryboardView {
    @IBOutlet weak var tableView: UITableView!
    private let viewWillAppearSubject = PublishSubject()
    private var routeSelectedSubject =
    PublishSubject()
    var disposeBag = DisposeBag()
    override func viewDidLoad() {
    super.viewDidLoad()
    }
    override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    viewWillAppearSubject.onNext(())
    }
    }
    extension FavoritesViewController: FavoritesViewRouting {
    var routeSelected: Observable {
    return routeSelectedSubject.asObservable()
    }
    }
    Add
    Add
    Add

    View Slide

  66. // MARK: - ReactorKit
    extension FavoritesViewController {
    func bind(reactor: FavoritesViewReactor) {
    // Action
    viewWillAppearSubject
    .map { Reactor.Action.actViewWillAppear }
    .bind(to: reactor.action)
    .disposed(by: disposeBag)
    self.tableView.rx.itemSelected
    .map { Reactor.Action.tapFavoritePhoto(row: $0.row) }
    .bind(to: reactor.action)
    .disposed(by: disposeBag)
    // State
    reactor.state.map { $0.route }
    .distinctUntilChanged()
    .filter { $0 != nil }
    .subscribe(onNext: { [weak self] in
    self?.routeSelectedSubject.onNext($0!)
    })
    .disposed(by: disposeBag)
    }
    }
    Add
    Add

    View Slide

  67. 3. ରԠ͢ΔCoordinator ͷมߋ (ϧʔςΟϯάՕॴ)
    func start() {
    let viewController = viewControllerFactory.favoriteTop()
    navigationController.pushViewController(viewController,
    animated: true)
    // NOTE: mod routing (replace reactor -> VC)
    _ = viewController
    .routeSelected
    .subscribe(onNext: { [weak self] route in
    guard let self = self else { return }
    switch route {
    case .photoDetail(let photoId):
    self.pushPhotoDetail(photoId: photoId)
    }
    })
    .disposed(by: disposeBag)
    }

    View Slide

  68. 3. ରԠ͢ΔCoordinator ͷมߋ (ϧʔςΟϯάՕॴ)
    func start() {
    let viewController = viewControllerFactory.favoriteTop()
    navigationController.pushViewController(viewController,
    animated: true)
    // NOTE: mod routing (replace reactor -> VC)
    _ = viewController
    .routeSelected
    .subscribe(onNext: { [weak self] route in
    guard let self = self else { return }
    switch route {
    case .photoDetail(let photoId):
    self.pushPhotoDetail(photoId: photoId)
    }
    })
    .disposed(by: disposeBag)
    }

    View Slide

  69. DI(v2) ͷ׬੒ʂ

    View Slide

  70. View Slide

  71. ·ͱΊ

    View Slide

  72. ·ͱΊ
    • DIͷಋೖ͸೉͘͠ͳ͍
    • ϕωϑΟοτ͸͋ΔͷͰɺҰ౓ಋೖΛݕ౼͢ΔͱΑ
    ͍ͱࢥ͍·͢
    • ʢݸਓతͳҙݟͱͯ͠͸ʣv1͕μϝͱ͸ࢥ͍ͬͯͳ͍
    • νʔϜͰ߹ҙΛͱΓɺͲͪΒ͔౷ҰͰ͖Ε͹OK

    View Slide

  73. ·ͱΊ
    ReactorKitΛ௨ͯ͠ɺ
    ʮઃܭʹ͍ͭͯνʔϜ಺Ͱٞ࿦͕׆ൃʹͳΔʯ
    ͱͨ͠Βɺͱͯ΋ྑ͍ͱࢥ͏

    View Slide


  74. ͝੩ௌ
    ͋Γ͕ͱ͏͍͟͝·ͨ͠

    View Slide