ReactorKitを実戦投入してみて

 ReactorKitを実戦投入してみて

ReactorKit Meetup Japan 18/06/28
https://www.wantedly.com/companies/wantedly/post_articles/127635

English ver: https://speakerdeck.com/yusuga/practical-application-of-reactorkit

次: iOSアプリのアーキテクチャについて考えてたどり着いた結論
https://speakerdeck.com/yusuga/final-conclusion-of-the-ios-architecture

Df8bc8c531e2c5c89c1a007db1cf79a3?s=128

Yu Sugawara

June 28, 2018
Tweet

Transcript

  1. ReactorKitΛ࣮ઓ౤ೖͯ͠Έͯ ReactorKit Meetup Japan 18/06/28

  2. ੁݪ ༞ @yusuga_

  3. • ߹ಉձࣾϐίε • ࡳຈ • 3ਓ: iOS / Web։ൃ /

    WebσβΠφ • Ҋ݅ืूதʂ
  4. None
  5. ReactorKitΛ࣮ઓ౤ೖͯ͠Έͯ

  6. ࠾༻ͨ͠ཧ༝

  7. ʓʓͷਖ਼͍͠ઃܭ͸Θ͔Γ·͔͢ʁ

  8. ʓʓͷਖ਼͍͠ઃܭ͸Θ͔Γ·͔͢ʁ • ʓʓ → MVC, MVVM, MVP, Clean Architecture, VIPER

    • ֶशίετ • ϩʔΧϧ/ࣾ಺ϧʔϧ͕௥Ճ͞Εɺ͞Βʹֶशίετ
  9. ΞʔΩςΫνϟΛਖ਼࣮͘͠૷͢Δ͜ ͱ͸೉͍͠

  10. • ReactorKit͸ϦΞΫςΟϒͰ୯ํ޲ͷΞʔΩςΫνϟΛߏங͢Δ ͨΊͷFramework

  11. ܰྔͳΞʔΩςΫνϟ

  12. ܰྔͳΞʔΩςΫνϟ • 2ͭͷϓϩτίϧͷΈ 1. View protocol (StoryboardView protocol) 2. Reactor

    protocol • ొ৔ਓ෺͕গͳ͍ͷͰཧղ͠΍͍͢
  13. ܰྔͳΞʔΩςΫνϟ ϨΠϠ ໾ׂ View ActionͷൃߦɺStateΛදࣔ෦ʹ൓ө Reactor Action͔ΒStateΛߋ৽ Service ϏδωεϩδοΫɻ ྫ͑͹ɺAPIϦΫΤετɺDBߋ৽ͳͲ

  14. ܰྔͳΞʔΩςΫνϟ ϨΠϠ ߏஙํ๏ View View protocolʹ४ڌ Reactor Reactor protocolʹ४ڌ Service

    Protocol͸༻ҙ͞Ε͍ͯͳ͍͕ɺReactor಺ͷϏδωεϩδ οΫΛҠৡ͢ΔϨΠϠ (೚ҙ)
  15. ܰྔͳΞʔΩςΫνϟ • ϓϩδΣΫτͷن໛ʹΑͬͯ͸... • ཻ౓͕େ͖͗ͯ͢ٯʹෳࡶʹʁ • ݸਓతʹ͸ͪΐ͏Ͳ͍͍όϥϯεͩͬͨ

  16. ମܥతͳαϯϓϧ • Counter • ࠷খݶͷ࣮૷ • GitHubSearch • GithubͷϦϙδτϦΛݕࡧ͢Δγϯϓϧͳඇಉظॲཧͷ࣮૷

  17. ମܥతͳαϯϓϧ • RxTodo • ServiceϨΠϠͰTableViewͷॲཧΛ࣮૷ • Cleverbot • ServiceϨΠϠͰAPIϦΫΤετΛ࣮૷ •

    Drrrible • App StoreʹϦϦʔεࡁΈͷΞϓϦͰɺΑΓ࣮ફతͳ࣮૷͕֬ ೝͰ͖Δ
  18. ೔ຊޠͷهࣄ • ReactorKit(Flux + Reactive Programming)ΛֶͿ1 ೖ໳ฤ • ReactorKit(Flux +

    Reactive Programming)ΛֶͿ2 جૅฤ • ReactorKit(Flux + Reactive Programming)ΛֶͿ3 ࣮ફฤ • ͦͷଞ: iOSΞϓϦͷΞʔΩςΫνϟʹ͍ͭͯߟ͑Δ • MVC2, MVVM, ReactorKitͷൺֱ
  19. ಋೖͯ͠೰Μͩ͜ͱ

  20. ಋೖͯ͠೰Μͩ͜ͱ • 1.5ਓ݄ن໛ͷϓϩδΣΫτʹಋೖ • ࣮ࡍʹಋೖͨ͠ޙʹݟ͍ͯͨͩ͘ͱΑΓϐϯͱ͘Δ͔΋

  21. ! Q #1: ը໘ભҠ͸୭͕ߦ͏ʁ

  22. ! Q #1: ը໘ભҠ͸୭͕ߦ͏ʁ • ྫ͑͹MVVMͩͱ... • ViewModel • ViewؒΛૄ݁߹ʹ͠Α͏ͱ͍͏ߟ͑ํ

    • kicksterter/ios-oss ͸View(UIViewController)Ͱը໘ભҠ
  23. ! Q #1: ը໘ભҠ͸୭͕ߦ͏ʁ • ! A. ViewϨΠϠ • Reactor͸UIKitΛimportͯ͠͸͍͚ͳ͍

  24. RxTodo

  25. RxTodo - TaskListViewController self.addButtonItem.rx.tap .map(reactor.reactorForCreatingTask) .subscribe(onNext: { [weak self] reactor

    in guard let `self` = self else { return } let viewController = TaskEditViewController(reactor: reactor) let navigationController = UINavigationController(rootViewController: viewController) self.present(navigationController, animated: true, completion: nil) }) .disposed(by: self.disposeBag)
  26. ! Q #2: Alertදࣔ͸୭͕ߦ͏ʁ

  27. RxTodo

  28. ! Q #2: Alertදࣔ͸୭͕ߦ͏ʁ • Alert΋ը໘ભҠͳͷͰViewϨΠϠ...? let alert = AlertService(provider:

    reactor.provider) let alertActions: [TaskEditViewCancelAlertAction] = [.leave, .stay] self.cancelButtonItem.rx.tap .flatMap { alert.show( title: "Really?", message: "All changes will be lost", preferredStyle: .alert, actions: alertActions) } .filter { $0 == .leave } .map { _ in Reactor.Action.cancel } .bind(to: reactor.action) .disposed(by: self.disposeBag)
  29. ! Q #2: Alertදࣔ͸୭͕ߦ͏ʁ • ! A. ServiceϨΠϠ

  30. RxTodo - TaskEditViewController final class TaskEditViewController: BaseViewController, View { func

    bind(reactor: TaskEditViewReactor) { self.cancelButtonItem.rx.tap .map { Reactor.Action.cancel } .bind(to: reactor.action) .disposed(by: self.disposeBag) } }
  31. RxTodo - TaskEditViewReactor case .cancel: if !self.currentState.shouldConfirmCancel { return .just(.dismiss)

    // no need to confirm } let alertActions: [TaskEditViewCancelAlertAction] = [.leave, .stay] return self.provider.alertService .show( title: "Really?", message: "All changes will be lost", preferredStyle: .alert, actions: alertActions ) .flatMap { alertAction -> Observable<Mutation> in switch alertAction { case .leave: return .just(.dismiss) case .stay: return .empty() } }
  32. AlertService • RxTodo/Sources/Services/AlertServiceͷ࣮૷͕ૉ੖Β͗͢͠Δ • AlertͷActionΛܕ҆શʹͰ͖Δ

  33. AlertͷActionΛܕ҆શʹͰ͖Δ case .cancel: if !self.currentState.shouldConfirmCancel { return .just(.dismiss) // no

    need to confirm } let alertActions: [TaskEditViewCancelAlertAction] = [.leave, .stay] return self.provider.alertService .show( title: "Really?", message: "All changes will be lost", preferredStyle: .alert, actions: alertActions ) .flatMap { alertAction -> Observable<Mutation> in switch alertAction { case .leave: return .just(.dismiss) case .stay: return .empty() } }
  34. ServiceϨΠϠͰAlertදࣔ • ࢍ൱͸͋Γͦ͏ • ͜Εʹ͍ͭͯ͸೰ΜͩͷͰReactorKitΛֶͿ3 ࣮ફฤʹॻ͍ͨ • Ϣʔβૢ࡞Λ൐͏෭࡞༻΋Action stream಺ͷ൓Ԡ(React)ͷҰͭ ʹա͗ͳ͍ͱߟ͑Δ

    • ྫ͑͹ผը໘Λݺͼग़͢ೝূϑϩʔ΋൓Ԡͱଊ͑Δ͜ͱ΋Ͱ ͖Δ
  35. Drrrible

  36. Drrrible - AuthService final class AuthService: AuthServiceType { func authorize()

    -> Observable<Void> { // ...... let safariViewController = SFSafariViewController(url: url) let navigationController = UINavigationController(rootViewController: safariViewController) navigationController.isNavigationBarHidden = true self.navigator.present(navigationController) self.currentViewController = navigationController return self.callbackSubject .flatMap(self.accessToken) .do(onNext: { [weak self] accessToken in try self?.saveAccessToken(accessToken) self?.currentAccessToken = accessToken }) .map { _ in } } }
  37. Drrrible - AuthService final class AuthService: AuthServiceType { fileprivate let

    callbackSubject = PublishSubject<String>() }
  38. Drrrible - AuthService final class AuthService: AuthServiceType { func callback(code:

    String) { self.callbackSubject.onNext(code) self.currentViewController?.dismiss(animated: true, completion: nil) self.currentViewController = nil } }
  39. Drrrible - LoginViewReactor final class LoginViewReactor: Reactor, ServiceContainer { func

    mutate(action: Action) -> Observable<Mutation> { switch action { case .login: let setLoading: Observable<Mutation> = .just(Mutation.setLoading(true)) let setLoggedIn: Observable<Mutation> = self.authService.authorize() .flatMap { self.userService.fetchMe() } .map { true } .catchErrorJustReturn(false) .map(Mutation.setLoggedIn) return setLoading.concat(setLoggedIn) } } }
  40. ! Q #3: Realmͱͷ૊Έ߹Θͤํ͸ʁ

  41. RealmͷNotification͕༏ल let results = realm.objects(Person.self).filter("age > 5") notificationToken = results.observe

    { [weak self] (changes: RealmCollectionChange) in guard let tableView = self?.tableView else { return } switch changes { case .initial: tableView.reloadData() case .update(_, let deletions, let insertions, let modifications): tableView.beginUpdates() tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }), with: .automatic) tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}), with: .automatic) tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }), with: .automatic) tableView.endUpdates() case .error(let error): fatalError("\(error)") } } https://realm.io/docs/swift/latest/#notifications
  42. ! Q #3: Realmͱͷ૊Έ߹Θͤํ͸ʁ • ! A. ServiceϨΠϠ͔ΒͷGlobal Event

  43. Global Event

  44. RxTodo - TaskService

  45. ! Q #4: NoActionͷReactor͸ඞཁʁ

  46. ReactorKit - Reactor public struct NoAction {}

  47. Cleverbot - MessageCellReactor final class MessageCellReactor: Reactor { typealias Action

    = NoAction struct State { var message: String? } let initialState: State init(message: Message) { self.initialState = State(message: message.text) } }
  48. ! Q #4: NoActionͷReactor͸ඞཁʁ • ! A. ඞਢͰ͸ͳ͍͚Ͳɺ͍͔ͭ͘ͷར఺΋ߟ͑ΒΕΔ

  49. ! Q #4: NoActionͷReactor͸ඞཁʁ • Design Goal (ެࣜυΩϡϝϯτΑΓ) • Start

    Small: ReactorKit doesn't require the whole application to follow a single architecture. ReactorKit can be > adopted partially, for one or more specific views. You don't need to rewrite everything to use ReactorKit on your existing project. ReactorKit - README.md#design-goal
  50. NoActionͷReactorΛ࡞Δཧ༝ • UIίϯϙʔωϯτΛReactiveʹ͍ͨ͠ • ྫ͑͹Global Event͔ΒͷมߋʹରԠ • ߏ଄ΛकΔ View :

    Reactor = 1 : 1 • ܕʹ͸Ίͨઃܭ͸ָ
  51. ! Q #5: UITableViewCellͷReactor ͸୭͕؅ཧ͢Δʁ

  52. ! Q #5: UITableViewCellͷReactor͸୭͕؅ཧ͢Δʁ • ྫ͑͹ɺUITableViewControllerͷ Reactorͱෳ਺ͷUITableViewCellͷ Reactorͱ͍͏ߏ੒ • UITableViewCellͷReactor͸ಈతʹ૿

    ݮ Cleverbot
  53. ! Q #5: UITableViewCellͷReactor͸୭͕؅ཧ͢Δʁ • ! A. UITableViewControllerͷReactor Cleverbot

  54. Cleverbot - ChatViewSection struct ChatViewSection { var items: [ChatViewSectionItem] }

    enum ChatViewSectionItem { case incomingMessage(MessageCellReactor) case outgoingMessage(MessageCellReactor) }
  55. Cleverbot - ChatViewReactor final class ChatViewReactor: Reactor { struct State

    { var sections: [ChatViewSection] = [ChatViewSection(items: [])] var cleverbotState: String? = nil } }
  56. 6. Dependency Injection

  57. Drrrible (0.2.0) - ServiceContainer let DI = Container().then { $0.register(Networking<DribbbleAPI>.self)

    { _ in Networking(plugins: [AuthPlugin()]) } .inObjectScope(.container) $0.autoregister(AuthServiceType.self, initializer: AuthService.init) .inObjectScope(.container) $0.autoregister(UserServiceType.self, initializer: UserService.init) .inObjectScope(.container) $0.autoregister(ShotServiceType.self, initializer: ShotService.init) .inObjectScope(.container) $0.autoregister(AppStoreServiceType.self, initializer: AppStoreService.init) .inObjectScope(.container) }
  58. Drrrible (0.2.0) - ServiceContainer protocol ServiceContainer {} extension ServiceContainer {

    var networking: Networking<DribbbleAPI> { return DI.resolve(Networking<DribbbleAPI>.self)! } var authService: AuthServiceType { return DI.resolve(AuthServiceType.self)! } var userService: UserServiceType { return DI.resolve(UserServiceType.self)! } var shotService: ShotServiceType { return DI.resolve(ShotServiceType.self)! } var appStoreService: AppStoreServiceType { return DI.resolve(AppStoreServiceType.self)! } }
  59. SwinjectStoryboard (ࢼ͠ʹಋೖ) extension SwinjectStoryboard { @objc class func setup() {

    defaultContainer.register(AppRootViewReactor.self) { _ in AppRootViewReactor() } defaultContainer.storyboardInitCompleted(AppRootViewController.self) { $1.reactor = $0.resolve(AppRootViewReactor.self) } } }
  60. DI͸೰Έத • ࠷৽ͷDrrrible͸Swinjectʹґଘ͍ͯ͠ͳ͍ • Remove Swinject @devxoul committed on 16

    Aug 2017 • commit͔Β͸ͦͷ൑அʹࢸͬͨܦա͸ಡΈऔΕͳ͔ͬͨͷ Ͱɺޙ΄Ͳ devxoul ͞Μʹ࣭໰͠ʹߦ͘ • devxoul/PureΛಋೖ༧ఆʁ • Pure DI͸DIίϯςφͷͳ͍DI
  61. ՝୊ • transformͷઃܭ͕೉͍͠ • viewWillAppear() Ͱ reflash ͨ͠ํ͕͸Δ͔ʹ؆୯ • ৄ͘͠͸ɺReactorKitΛֶͿ3

    ࣮ફฤ • େن໛ʹͳͬͨ࣌ͷύϑΥʔϚϯε͕ؾʹͳΔ
  62. ·ͱΊ

  63. ·ͱΊ • ReactorKitͷܕʹ͸Ίͨઃܭ͸ָ • Protocolʹ४ڌ͢Δ͚ͩͳͷͰॊೈͳઃܭ͕Մೳ • ୯ํ޲ετϦʔϜ͸γϯϓϧͰ੔ཧ͠΍͍͢ • ΞʔΩςΫνϟߏஙͷίʔυྔΛ࡟ݮ

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