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

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

Yu Sugawara

June 28, 2018
Tweet

More Decks by Yu Sugawara

Other Decks in Programming

Transcript

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

    • ֶशίετ • ϩʔΧϧ/ࣾ಺ϧʔϧ͕௥Ճ͞Εɺ͞Βʹֶशίετ
  2. ܰྔͳΞʔΩςΫνϟ ϨΠϠ ߏஙํ๏ View View protocolʹ४ڌ Reactor Reactor protocolʹ४ڌ Service

    Protocol͸༻ҙ͞Ε͍ͯͳ͍͕ɺReactor಺ͷϏδωεϩδ οΫΛҠৡ͢ΔϨΠϠ (೚ҙ)
  3. ೔ຊޠͷهࣄ • ReactorKit(Flux + Reactive Programming)ΛֶͿ1 ೖ໳ฤ • ReactorKit(Flux +

    Reactive Programming)ΛֶͿ2 جૅฤ • ReactorKit(Flux + Reactive Programming)ΛֶͿ3 ࣮ફฤ • ͦͷଞ: iOSΞϓϦͷΞʔΩςΫνϟʹ͍ͭͯߟ͑Δ • MVC2, MVVM, ReactorKitͷൺֱ
  4. 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)
  5. ! 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)
  6. 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) } }
  7. 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() } }
  8. 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() } }
  9. 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 } } }
  10. Drrrible - AuthService final class AuthService: AuthServiceType { func callback(code:

    String) { self.callbackSubject.onNext(code) self.currentViewController?.dismiss(animated: true, completion: nil) self.currentViewController = nil } }
  11. 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) } } }
  12. 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
  13. 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) } }
  14. ! 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
  15. Cleverbot - ChatViewSection struct ChatViewSection { var items: [ChatViewSectionItem] }

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

    { var sections: [ChatViewSection] = [ChatViewSection(items: [])] var cleverbotState: String? = nil } }
  17. 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) }
  18. 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)! } }
  19. SwinjectStoryboard (ࢼ͠ʹಋೖ) extension SwinjectStoryboard { @objc class func setup() {

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

    Aug 2017 • commit͔Β͸ͦͷ൑அʹࢸͬͨܦա͸ಡΈऔΕͳ͔ͬͨͷ Ͱɺޙ΄Ͳ devxoul ͞Μʹ࣭໰͠ʹߦ͘ • devxoul/PureΛಋೖ༧ఆʁ • Pure DI͸DIίϯςφͷͳ͍DI