Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS Test Night #3: ViewController ⇄ State by RxSwift

iOS Test Night #3: ViewController ⇄ State by RxSwift

Takeshi Ihara

March 13, 2017
Tweet

More Decks by Takeshi Ihara

Other Decks in Programming

Transcript

  1. RxSwift Rx is a generic abstraction of computation expressed through

    Observable<Element> interface. This is a Swift version of Rx.
  2. prepare VC viewWillAppear Presenter → Event State ← prepared "1*$BMM

    Refresh View touch touched %POPUIJOH Refresh View touchEvent
  3. class Presenter { enum State { case initial case prepared(value:

    Int) case touched } enum Event { case prepare case touch } let eventReciever = PublishSubject<Event>() private let innerViewRefrector = BehaviorSubject<State>(value: .initial) private let disposeBag = DisposeBag() var viewRefrector: Observable<State> { return innerViewRefrector.asObservable() } init() { eventReciever .flatMap { event -> Observable<State> in switch event { case .prepare: return [API Call] .map { .prepared(value: $0) } case .touch: return Observable.just(.touched) } } .bindTo(innerViewRefrector) .addDisposableTo(disposeBag) } }
  4. class Presenter { enum State { // VCʹฦ͢ঢ়ଶ case initial

    case prepared(value: Int) case touched } enum Event { // VC͔Βड͚औΔΠϕϯτ case prepare case touch } 
 … }
  5. class Presenter { … let eventReciever = PublishSubject<Event>() private let

    innerViewRefrector = BehaviorSubject<State>(value: .initial) private let disposeBag = DisposeBag() var viewRefrector: Observable<State> { return innerViewRefrector.asObservable() } … } ௨஌ɾड৴ܥͷαϒδΣΫτ
  6. class Presenter { … init() { eventReciever .flatMap { event

    -> Observable<State> in switch event { case .prepare: return [API Call] .map { .prepared(value: $0) } case .touch: return Observable.just(.touched) } } .bindTo(innerViewRefrector) .addDisposableTo(disposeBag) } }
  7. class Presenter { enum State { case initial case prepared(value:

    Int) case touched } enum Event { case prepare case touch } let eventReciever = PublishSubject<Event>() private let innerViewRefrector = BehaviorSubject<State>(value: .initial) private let disposeBag = DisposeBag() var viewRefrector: Observable<State> { return innerViewRefrector.asObservable() } init() { eventReciever .flatMap { event -> Observable<State> in switch event { case .prepare: return [API Call] .map { .prepared(value: $0) } case .touch: return Observable.just(.touched) } } .bindTo(innerViewRefrector) .addDisposableTo(disposeBag) } } VCʹฦ͢ঢ়ଶ VC͔Βड͚औΔΠϕϯτ ௨஌ɾड৴ܥͷαϒδΣΫτ ΠϕϯτΛड͚औͬͯ ঢ়ଶΛฦ͢
  8. class ViewController: UIViewController { typealias Event = Presenter.Event var presenter:

    Presenter! private let disposeBag = DisposeBag() override func viewDidLoad() { setupUIBindings() setupEventBindings() } private func setupUIBindings() { presenter.viewRefrector .asDriver(onErrorDriveWith: .empty()) .drive( onNext: { state in switch state { case .initial: break case .prepared(let value): // API͔Βͷ݁ՌΛ༻͍ͯViewʹ൓ө͢Δ case .touched: // Touchͷ݁ՌΛViewʹ൓ө͢Δ } } ) .addDisposableTo(disposeBag) } private func setupEventBindings() { rx.sentMessage(#selector(UIViewController.viewWillAppear(_:))) .map { _ in () } .shareReplay(1) .map { return Event.prepare } .bindTo(presenter.eventReciever) .addDisposableTo(disposeBag) btn.rx.tap .map { return Event.touch } .bindTo(presenter.eventReciever) .addDisposableTo(disposeBag) } }
  9. class ViewController: UIViewController { typealias Event = Presenter.Event var presenter:

    Presenter! private let disposeBag = DisposeBag() override func viewDidLoad() { setupUIBindings() setupEventBindings() } … }
  10. class ViewController: UIViewController { … private func setupUIBindings() { presenter.viewRefrector

    .asDriver(onErrorDriveWith: .empty()) .drive( onNext: { state in switch state { case .initial: break case .prepared(let value): // API͔Βͷ݁ՌΛ༻͍ͯViewʹ൓ө͢Δ case .touched: // Touchͷ݁ՌΛViewʹ൓ө͢Δ } } ) .addDisposableTo(disposeBag) } … }
  11. class ViewController: UIViewController { … private func setupEventBindings() { rx.sentMessage(#selector(UIViewController.viewWillAppear(_:)))

    // viewWillAppearΛPresenterʹྲྀͯ͠Δ .map { _ in () } .shareReplay(1) .map { return Event.prepare } .bindTo(presenter.eventReciever) .addDisposableTo(disposeBag) btn.rx.tap // ϘλϯͷλονΠϕϯτΛPresenterʹྲྀͯ͠Δ .map { return Event.touch } .bindTo(presenter.eventReciever) .addDisposableTo(disposeBag) } }
  12. class ViewController: UIViewController { typealias Event = Presenter.Event var presenter:

    Presenter! private let disposeBag = DisposeBag() override func viewDidLoad() { setupUIBindings() setupEventBindings() } private func setupUIBindings() { presenter.viewRefrector .asDriver(onErrorDriveWith: .empty()) .drive( onNext: { state in switch state { case .initial: break case .prepared(let value): // API͔Βͷ݁ՌΛ༻͍ͯViewʹ൓ө͢Δ case .touched: // Touchͷ݁ՌΛViewʹ൓ө͢Δ } } ) .addDisposableTo(disposeBag) } private func setupEventBindings() { rx.sentMessage(#selector(UIViewController.viewWillAppear(_:))) .map { _ in () } .shareReplay(1) .map { return Event.prepare } .bindTo(presenter.eventReciever) .addDisposableTo(disposeBag) btn.rx.tap .map { return Event.touch } .bindTo(presenter.eventReciever) .addDisposableTo(disposeBag) } } StateΛड͚औͬͯ ViewΛ൓ө͢Δ EventΛૹΔ
  13. prepare VC viewWillAppear Presenter → Event State ← prepared "1*$BMM

    Refresh View touch touched %POPUIJOH Refresh View touchEvent
  14. prepare VC viewWillAppear Presenter → Event State ← prepared "1*$BMM

    Refresh View touch touched %POPUIJOH Refresh View touchEvent
  15. %POPUIJOH "1*$BMM VC viewWillAppear Presenter → Event State ← prepare

    prepared Refresh View touch touched Refresh View touchEvent Test Case Test Case
  16. // 5FTUBCMF0CTFSWBCMF1SFTFOUFS4UBUFΛੜ੒ let observer = scheduler.createObserver(Presenter.State.self) let xs = scheduler.createColdObservable([

    // Ծ૝࣌ࠁ100ޙʹprepareΠϕϯτΛૹΔ next(100, Presenter.UserEvent.prepare) ]) // Ծ૝࣌ࠁ100ʹxsΛeventReceiverʹbinding͢Δ // ஗ԆධՁ͕૸Δ scheduler.scheduleAt(100) { xs .bindTo(presenter.eventReceiver) .addDisposableTo(disposeBag) } // WJFX3FGMFDUFSΛobserverʹSubscribeͤ͞Δ scheduler.scheduleAt(200) { presenter.viewReflecter .subscribe(observer) .addDisposableTo(disposeBag) }
  17. class PresenterSpec: QuickSpec { override func spec() { describe("Presenter") {

    var presenter: Presenter! var scheduler: TestScheduler! var disposeBag: DisposeBag! beforeEach { scheduler = TestScheduler(initialClock: 0) presenter = Presenter() disposeBag = DisposeBag() } context("when prepare") { it("prepared") { let observer = scheduler.createObserver(Presenter.State.self) let xs = scheduler.createColdObservable([ next(100, Presenter.UserEvent.prepare) ]) scheduler.scheduleAt(100) { xs .bindTo(presenter.eventReceiver) .addDisposableTo(disposeBag) } scheduler.scheduleAt(200) { presenter.viewReflecter .subscribe(observer) .addDisposableTo(disposeBag) } scheduler.start() expect(observer.events.count).to(equal(2)) expect(observer.events[0].time).to(equal(200)) expect(observer.events[1].time).to(equal(300)) let initial = observer.events[0].value.element expect(initial).toNot(beNil()) expect(initial!).to(equal(Presenter.State.initial)) let subject = observer.events[1].value.element expect(subject).toNot(beNil()) expect(subject!).to(equal(Presenter.State.prepared(value: 1))) } } } } }
  18. class PresenterSpec: QuickSpec { override func spec() { describe("Presenter") {

    var presenter: Presenter! var scheduler: TestScheduler! var disposeBag: DisposeBag! beforeEach { scheduler = TestScheduler(initialClock: 0) presenter = Presenter() disposeBag = DisposeBag() } … } } }
  19. class PresenterSpec: QuickSpec { override func spec() { describe("Presenter") {

    … context("when prepare") { it("prepared") { let observer = scheduler.createObserver(Presenter.State.self) let xs = scheduler.createColdObservable([ next(100, Presenter.UserEvent.prepare) ]) scheduler.scheduleAt(100) { xs .bindTo(presenter.eventReceiver) .addDisposableTo(disposeBag) } scheduler.scheduleAt(200) { presenter.viewReflecter .subscribe(observer) .addDisposableTo(disposeBag) } scheduler.start() … } } } } }
  20. class PresenterSpec: QuickSpec { override func spec() { describe("Presenter") {

    … context("when prepare") { it("prepared") { … expect(observer.events.count).to(equal(2)) expect(observer.events[0].time).to(equal(200)) expect(observer.events[1].time).to(equal(200)) let initial = observer.events[0].value.element expect(initial).toNot(beNil()) expect(initial!).to(equal(Presenter.State.initial)) let subject = observer.events[1].value.element expect(subject).toNot(beNil()) expect(subject!).to(equal(Presenter.State.prepared(value: 1))) } } } } } ड͚ͱͬͨঢ়ଶͷ ൃੜͨ࣌͠ࠁɾछྨΛݕূ
  21. class PresenterSpec: QuickSpec { override func spec() { describe("Presenter") {

    var presenter: Presenter! var scheduler: TestScheduler! var disposeBag: DisposeBag! beforeEach { scheduler = TestScheduler(initialClock: 0) presenter = Presenter() disposeBag = DisposeBag() } context("when prepare") { it("prepared") { let observer = scheduler.createObserver(Presenter.State.self) let xs = scheduler.createColdObservable([ next(100, Presenter.UserEvent.prepare) ]) scheduler.scheduleAt(100) { xs .bindTo(presenter.eventReceiver) .addDisposableTo(disposeBag) } scheduler.scheduleAt(200) { presenter.viewReflecter .subscribe(observer) .addDisposableTo(disposeBag) } scheduler.start() expect(observer.events.count).to(equal(2)) expect(observer.events[0].time).to(equal(200)) expect(observer.events[1].time).to(equal(200)) let initial = observer.events[0].value.element expect(initial).toNot(beNil()) expect(initial!).to(equal(Presenter.State.initial)) let subject = observer.events[1].value.element expect(subject).toNot(beNil()) expect(subject!).to(equal(Presenter.State.prepared(value: 1))) } } } } } ड͚ͱͬͨঢ়ଶͷ ൃੜͨ࣌͠ࠁɾछྨΛݕূ Ծ૝࣌ࠁ্Ͱͷ ΠϕϯτϋϯυϦϯάΛઃఆ