26 Oct 2017

RxFeedback – The simplest architecture for RxSwift

Difference from other architectures

  • Elm – pretty close, feedback loops for effects instead of Cmd, which effects to perform are encoded into state and querried by feedback loops
  • Redux – kind of like this, but feedback loops instead of middleware
  • Redux-Observable – observables observe state vs. being inside middleware between view and state
  • Cycle.js – no simple explanation :), ask @andrestaltz
  • MVVM – separates state from effects and doesn’t require a view
 typealias Feedback<State, Event> = (Observable<State>) -> Observable<Event> public static func system<State, Event>( initialState: State, reduce: @escaping (State, Event) -> State, feedback: Feedback<State, Event>... ) -> Observable<State>

Why

  • Straightforward
    • if it’s state -> State
    • if it’s a way to modify state -> Event/Command
    • it it’s an effect -> encode it into part of state and then design a feedback loop
  • Declarative
    • System behavior is first declaratively specified and effects begin after subscribe is called => Compile time proof there are no “unhandled states”
  • Debugging is easier
    • A lot of logic is just normal pure function that can be debugged using Xcode debugger, or just printing the commands.
  • Can be applied on any level
    • Entire system
    • application (state is stored inside a database, CoreData, Firebase, Realm)
    • view controller (state is stored inside system operator)
    • inside feedback loop (another system operator inside feedback loop)
  • Works awesome with dependency injection
  • Testing
    • Reducer is a pure function, just call it and assert results
    • In case effects are being tested -> TestScheduler
  • Can model circular dependencies
  • Completely separates business logic from effects (Rx).
    • Business logic can be transpiled between platforms (ShiftJS, C++, J2ObjC)

Examples

Simple UI Feedback loop

Observable.system(
    initialState: 0,
    reduce: { (state, event) -> State in
            switch event {
            case .increment:
                return state + 1
            case .decrement:
                return state - 1
            }
        },
    scheduler: MainScheduler.instance,
    feedback:
        // UI is user feedback
        bind { state in
            ([
                state.map(String.init).bind(to: label.rx.text)
            ], [
                plus.rx.tap.map { Event.increment },
                minus.rx.tap.map { Event.decrement }
            ])
        }
    )

Play Catch

Simple automatic feedback loop.

Observable.system(
    initialState: State.humanHasIt,
    reduce: { (state: State, event: Event) -> State in
        switch event {
            case .throwToMachine:
                return .machineHasIt
            case .throwToHuman:
                return .humanHasIt
            }
        },
    scheduler: MainScheduler.instance,
    feedback:
        // UI is human feedback
        bindUI,
        // NoUI, machine feedback
        react(query: { $0.machinePitching }, effects: { () -> Observable<Event> in
            return Observable<Int>
                .timer(1.0, scheduler: MainScheduler.instance)
                .map { _ in Event.throwToHuman }
        })
)

Paging

Driver.system(
    initialState: State.empty,
    reduce: State.reduce,
    feedback:
        // UI, user feedback
        bindUI,
        // NoUI, automatic feedback
        react(query: { $0.loadNextPage }, effects: { resource in
            return URLSession.shared.loadRepositories(resource: resource)
                .asDriver(onErrorJustReturn: .failure(.offline))
                .map(Event.response)
        })
    )

Run RxFeedback.xcodeproj > Example to find out more.

Source: https://github.com/kzaher/RxFeedback

Leave a Reply

%d bloggers like this: