Swiftpack.co - Package - NoTests/RxFeedback.swift

RxFeedback

Travis CI platforms pod Carthage compatible Swift Package Manager compatible

20min video pitch

The simplest architecture for RxSwift

    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.

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

Github

link
Stars: 465
Help us keep the lights on

Dependencies

Used By

Total: 0

Releases

1.0.2 - Dec 6, 2017

  • Fixes duplicated plist inclusion.

1.0.1 - Nov 9, 2017

  • Fixes leak in Hashable overload of react feedback loop.
  • Shares final guard against reentrancy (if feedback loops have implementation issue and are synchronous) between all feedback loops.

1.0.0 - Oct 22, 2017

  • Deprecates UI.* in favor of free methods.
  • Deprecates feedback loops of the form Driver<State> -> Driver<Event> in favor of Driver<State> -> Signal<Event>.
  • Adaptations for RxSwift 4.0.0.

0.3.3 - Sep 10, 2017

  • Adds reentrancy guards to UI.bind feedback loop (Driver version).

0.3.2 - Sep 2, 2017

  • Includes additional reentrancy checks in DEBUG builds.