Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

ੁݪ ༞ @yusuga_

Slide 3

Slide 3 text

• ߹ಉձࣾϐίε • ࡳຈ • 3ਓ: iOS / Web։ൃ / WebσβΠφ • Ҋ݅ืूதʂ

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

ReactorKitΛ࣮ઓ౤ೖͯ͠Έͯ

Slide 6

Slide 6 text

࠾༻ͨ͠ཧ༝

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

ʓʓͷਖ਼͍͠ઃܭ͸Θ͔Γ·͔͢ʁ • ʓʓ → MVC, MVVM, MVP, Clean Architecture, VIPER • ֶशίετ • ϩʔΧϧ/ࣾ಺ϧʔϧ͕௥Ճ͞Εɺ͞Βʹֶशίετ

Slide 9

Slide 9 text

ΞʔΩςΫνϟΛਖ਼࣮͘͠૷͢Δ͜ ͱ͸೉͍͠

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

ܰྔͳΞʔΩςΫνϟ

Slide 12

Slide 12 text

ܰྔͳΞʔΩςΫνϟ • 2ͭͷϓϩτίϧͷΈ 1. View protocol (StoryboardView protocol) 2. Reactor protocol • ొ৔ਓ෺͕গͳ͍ͷͰཧղ͠΍͍͢

Slide 13

Slide 13 text

ܰྔͳΞʔΩςΫνϟ ϨΠϠ ໾ׂ View ActionͷൃߦɺStateΛදࣔ෦ʹ൓ө Reactor Action͔ΒStateΛߋ৽ Service ϏδωεϩδοΫɻ ྫ͑͹ɺAPIϦΫΤετɺDBߋ৽ͳͲ

Slide 14

Slide 14 text

ܰྔͳΞʔΩςΫνϟ ϨΠϠ ߏஙํ๏ View View protocolʹ४ڌ Reactor Reactor protocolʹ४ڌ Service Protocol͸༻ҙ͞Ε͍ͯͳ͍͕ɺReactor಺ͷϏδωεϩδ οΫΛҠৡ͢ΔϨΠϠ (೚ҙ)

Slide 15

Slide 15 text

ܰྔͳΞʔΩςΫνϟ • ϓϩδΣΫτͷن໛ʹΑͬͯ͸... • ཻ౓͕େ͖͗ͯ͢ٯʹෳࡶʹʁ • ݸਓతʹ͸ͪΐ͏Ͳ͍͍όϥϯεͩͬͨ

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

ମܥతͳαϯϓϧ • RxTodo • ServiceϨΠϠͰTableViewͷॲཧΛ࣮૷ • Cleverbot • ServiceϨΠϠͰAPIϦΫΤετΛ࣮૷ • Drrrible • App StoreʹϦϦʔεࡁΈͷΞϓϦͰɺΑΓ࣮ફతͳ࣮૷͕֬ ೝͰ͖Δ

Slide 18

Slide 18 text

೔ຊޠͷهࣄ • ReactorKit(Flux + Reactive Programming)ΛֶͿ1 ೖ໳ฤ • ReactorKit(Flux + Reactive Programming)ΛֶͿ2 جૅฤ • ReactorKit(Flux + Reactive Programming)ΛֶͿ3 ࣮ફฤ • ͦͷଞ: iOSΞϓϦͷΞʔΩςΫνϟʹ͍ͭͯߟ͑Δ • MVC2, MVVM, ReactorKitͷൺֱ

Slide 19

Slide 19 text

ಋೖͯ͠೰Μͩ͜ͱ

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

! Q #1: ը໘ભҠ͸୭͕ߦ͏ʁ • ྫ͑͹MVVMͩͱ... • ViewModel • ViewؒΛૄ݁߹ʹ͠Α͏ͱ͍͏ߟ͑ํ • kicksterter/ios-oss ͸View(UIViewController)Ͱը໘ભҠ

Slide 23

Slide 23 text

! Q #1: ը໘ભҠ͸୭͕ߦ͏ʁ • ! A. ViewϨΠϠ • Reactor͸UIKitΛimportͯ͠͸͍͚ͳ͍

Slide 24

Slide 24 text

RxTodo

Slide 25

Slide 25 text

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)

Slide 26

Slide 26 text

! Q #2: Alertදࣔ͸୭͕ߦ͏ʁ

Slide 27

Slide 27 text

RxTodo

Slide 28

Slide 28 text

! 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)

Slide 29

Slide 29 text

! Q #2: Alertදࣔ͸୭͕ߦ͏ʁ • ! A. ServiceϨΠϠ

Slide 30

Slide 30 text

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) } }

Slide 31

Slide 31 text

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 in switch alertAction { case .leave: return .just(.dismiss) case .stay: return .empty() } }

Slide 32

Slide 32 text

AlertService • RxTodo/Sources/Services/AlertServiceͷ࣮૷͕ૉ੖Β͗͢͠Δ • AlertͷActionΛܕ҆શʹͰ͖Δ

Slide 33

Slide 33 text

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 in switch alertAction { case .leave: return .just(.dismiss) case .stay: return .empty() } }

Slide 34

Slide 34 text

ServiceϨΠϠͰAlertදࣔ • ࢍ൱͸͋Γͦ͏ • ͜Εʹ͍ͭͯ͸೰ΜͩͷͰReactorKitΛֶͿ3 ࣮ફฤʹॻ͍ͨ • Ϣʔβૢ࡞Λ൐͏෭࡞༻΋Action stream಺ͷ൓Ԡ(React)ͷҰͭ ʹա͗ͳ͍ͱߟ͑Δ • ྫ͑͹ผը໘Λݺͼग़͢ೝূϑϩʔ΋൓Ԡͱଊ͑Δ͜ͱ΋Ͱ ͖Δ

Slide 35

Slide 35 text

Drrrible

Slide 36

Slide 36 text

Drrrible - AuthService final class AuthService: AuthServiceType { func authorize() -> Observable { // ...... 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 } } }

Slide 37

Slide 37 text

Drrrible - AuthService final class AuthService: AuthServiceType { fileprivate let callbackSubject = PublishSubject() }

Slide 38

Slide 38 text

Drrrible - AuthService final class AuthService: AuthServiceType { func callback(code: String) { self.callbackSubject.onNext(code) self.currentViewController?.dismiss(animated: true, completion: nil) self.currentViewController = nil } }

Slide 39

Slide 39 text

Drrrible - LoginViewReactor final class LoginViewReactor: Reactor, ServiceContainer { func mutate(action: Action) -> Observable { switch action { case .login: let setLoading: Observable = .just(Mutation.setLoading(true)) let setLoggedIn: Observable = self.authService.authorize() .flatMap { self.userService.fetchMe() } .map { true } .catchErrorJustReturn(false) .map(Mutation.setLoggedIn) return setLoading.concat(setLoggedIn) } } }

Slide 40

Slide 40 text

! Q #3: Realmͱͷ૊Έ߹Θͤํ͸ʁ

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

! Q #3: Realmͱͷ૊Έ߹Θͤํ͸ʁ • ! A. ServiceϨΠϠ͔ΒͷGlobal Event

Slide 43

Slide 43 text

Global Event

Slide 44

Slide 44 text

RxTodo - TaskService

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

ReactorKit - Reactor public struct NoAction {}

Slide 47

Slide 47 text

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) } }

Slide 48

Slide 48 text

! Q #4: NoActionͷReactor͸ඞཁʁ • ! A. ඞਢͰ͸ͳ͍͚Ͳɺ͍͔ͭ͘ͷར఺΋ߟ͑ΒΕΔ

Slide 49

Slide 49 text

! 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

Slide 50

Slide 50 text

NoActionͷReactorΛ࡞Δཧ༝ • UIίϯϙʔωϯτΛReactiveʹ͍ͨ͠ • ྫ͑͹Global Event͔ΒͷมߋʹରԠ • ߏ଄ΛकΔ View : Reactor = 1 : 1 • ܕʹ͸Ίͨઃܭ͸ָ

Slide 51

Slide 51 text

! Q #5: UITableViewCellͷReactor ͸୭͕؅ཧ͢Δʁ

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Cleverbot - ChatViewSection struct ChatViewSection { var items: [ChatViewSectionItem] } enum ChatViewSectionItem { case incomingMessage(MessageCellReactor) case outgoingMessage(MessageCellReactor) }

Slide 55

Slide 55 text

Cleverbot - ChatViewReactor final class ChatViewReactor: Reactor { struct State { var sections: [ChatViewSection] = [ChatViewSection(items: [])] var cleverbotState: String? = nil } }

Slide 56

Slide 56 text

6. Dependency Injection

Slide 57

Slide 57 text

Drrrible (0.2.0) - ServiceContainer let DI = Container().then { $0.register(Networking.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) }

Slide 58

Slide 58 text

Drrrible (0.2.0) - ServiceContainer protocol ServiceContainer {} extension ServiceContainer { var networking: Networking { return DI.resolve(Networking.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)! } }

Slide 59

Slide 59 text

SwinjectStoryboard (ࢼ͠ʹಋೖ) extension SwinjectStoryboard { @objc class func setup() { defaultContainer.register(AppRootViewReactor.self) { _ in AppRootViewReactor() } defaultContainer.storyboardInitCompleted(AppRootViewController.self) { $1.reactor = $0.resolve(AppRootViewReactor.self) } } }

Slide 60

Slide 60 text

DI͸೰Έத • ࠷৽ͷDrrrible͸Swinjectʹґଘ͍ͯ͠ͳ͍ • Remove Swinject @devxoul committed on 16 Aug 2017 • commit͔Β͸ͦͷ൑அʹࢸͬͨܦա͸ಡΈऔΕͳ͔ͬͨͷ Ͱɺޙ΄Ͳ devxoul ͞Μʹ࣭໰͠ʹߦ͘ • devxoul/PureΛಋೖ༧ఆʁ • Pure DI͸DIίϯςφͷͳ͍DI

Slide 61

Slide 61 text

՝୊ • transformͷઃܭ͕೉͍͠ • viewWillAppear() Ͱ reflash ͨ͠ํ͕͸Δ͔ʹ؆୯ • ৄ͘͠͸ɺReactorKitΛֶͿ3 ࣮ફฤ • େن໛ʹͳͬͨ࣌ͷύϑΥʔϚϯε͕ؾʹͳΔ

Slide 62

Slide 62 text

·ͱΊ

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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