RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.

Overview
RxReduce Logo
Travis CI Build Status
Frameworks Carthage Compatible CocoaPods Compatible
Platform Platform
Licence License

About

RxReduce is a Reactive implementation of the state container pattern (like Redux). It is based on the simple concepts of state immutability and unidirectionnal data flow.

Architecture concerns

Since a few years there has been a lot, I mean a LOT, of blog posts, tutorials, books, conferences about adapting alternate architecture patterns to mobile applications. The idea behind all those patterns is to provide a better way to:

  • meet the SOLID requirements (Wikipedia)
  • produce a safer code by design
  • make our code more testable

The good old MVC tends to be replaced by MVP, MVVM or VIPER. I won't go into details about these ones as they are well documented. I think MVVM is currently the most trending pattern, mostly because of its similarities with MVC and MVP and its ability to leverage data binding to ease the data flow. Moreover it is pretty easy to be enhanced by a Coordinator pattern and Reactive programming.

Go check this project if you're interested in Reactive Coordinators (RxFlow) πŸ‘Œ

That said, there is at least one other architecture pattern that stands out a little bit: State Container.

One of the most famous exemple is Redux, but let's not be restrained by a specific implementation.

Some resources about state containers:

The main goals of this pattern are to:

  • expose a clear/reproductible data flow within your application
  • rely on a single source of truth: the state
  • leverage value types to handle the state immutability
  • promote functional programming, as the only way to mutate a state is to apply a free function: the reducer

I find this approach very interesting compared to the more traditional ones, because it takes care of the consistency of your application state. MVC, MVP, MVVM or VIPER help you slice your application into well defined layers but they don't guide you so much when it comes to handle the state of your app.

Reactive programming is a great companion to state container architectures because it can help to:

  • propage the state mutations
  • build asynchronous actions to mutate the state (for networking, persistence, ...)

RxReduce

RxReduce:

  • provides a generic store that can handle all kinds of states
  • exposes state mutation through a Reactive mechanism
  • provides a simple/unified way to mutate the state synchronously and asynchronously via Actions

Installation

Carthage

In your Cartfile:

github "RxSwiftCommunity/RxReduce"

CocoaPods

In your Podfile:

pod 'RxReduce'

The key principles

The core mechanisms of RxReduce are very straightforward:

Here is a little animation that explains the flow within a state container architecture:

StateContainerArchitectureFlow
  • The Store is the component that handles your state. It has only one input: the "dispatch()" function, that takes an Action as a parameter.
  • The only way to trigger a State mutation is to call this "dispatch()" function.
  • Actions are simple types with no business logic. They embed the payload needed to mutate the state
  • Only free and testable functions called Reducers (RxReduce !) can mutate a State. A "reduce()" function takes a State, an Action and returns a mutated State ... that simple. To be precise, a reducer returns a mutated sub-State of the State. In fact, there is one reducer per sub-State of the State. By sub-State, we mean all the properties that compose a State.
  • The Store will make sure you provide one and only one reducer per sub-State. It brings safety and consistency to your application's logic. Each reducer has a well defined scope.
  • Reducers cannot perform asynchronous logic, they can only mutate the state in a synchronous and readable way. Asynchronous work will be taken care of by Reactive Actions.
  • You can be notified of the state mutation thanks to a Observable<State> exposed by the Store.

How to use RxReduce

Code samples

How to declare a State

As the main idea of state containers is about immutability, avoiding reference type uncontrolled propagation and race conditions, a State must be a value type. Structs and Enums are great for that.

struct TestState: Equatable {
    var counterState: CounterState
    var userState: UserState
}

enum CounterState: Equatable {
    case empty
    case increasing (Int)
    case decreasing (Int)
}

enum UserState: Equatable {
    case loggedIn (name: String)
    case loggedOut
}

Making states Equatable is not mandatory but it will allow the Store not to emit new state values if there is no change between 2 actions. So I strongly recommand to conform to Equatable to minimize the number of view refreshes.

How to declare Actions

Actions are simple data types that embed a payload used in the reducers to mutate the state.

enum AppAction: Action {
    case increase(increment: Int)
    case decrease(decrement: Int)
    case logUser(user: String)
    case clear
}

How to declare Reducers

As I said, a reducer is a free function. These kind of functions takes a value, returns an idempotent value, and performs no side effects. Their declaration is not even related to a type definition. This is super convenient for testing πŸ‘

Here we define two reducers that will take care of their dedicated sub-State. The first one mutates the CounterState and the second one mutates the UserState.

func counterReduce (state: TestState, action: Action) -> CounterState {

    guard let action = action as? AppAction else { return state.counterState }

    var currentCounter = 0

    // we extract the current counter value from the current state
    switch state.counterState {
    case .decreasing(let counter), .increasing(let counter):
        currentCounter = counter
    default:
        currentCounter = 0
    }

    // according to the action we mutate the counter state
    switch action {
    case .increase(let increment):
        return .increasing(currentCounter+increment)
    case .decrease(let decrement):
        return .decreasing(currentCounter-decrement)
    case .clear:
        return .empty
    default:
        return state.counterState
    }
}

func userReduce (state: TestState, action: Action) -> UserState {

    guard let action = action as? AppAction else { return state.userState }

    // according to the action we mutate the users state
    switch action {
    case .logUser(let user):
        return .loggedIn(name: user)
    case .clear:
        return .loggedOut
    default:
        return state.userState
    }
}

Each of these Reducers will only handle the Actions it is responsible for, nothing less, nothing more.

How to declare a Store

RxReduce provides a generic Store that can handle your application's State. You only need to provide an initial State:

let store = Store<TestState>(withState: TestState(counterState: .empty, userState: .loggedOut))

How to aggregate sub-State mutations into a whole State

As we saw: a reducer takes care only of its dedicated sub-State. We will then define a bunch of reducers to handle the whole application's state mutations. So, we need a mechanism to assemble all the mutated sub-State to a consistent State.

We will use functional programming technics to achieve that.

Lenses

A Lens is a generic way to access and mutate a value type in functional programming. It's about telling the Store how to mutate a certain sub-State of the State. For instance the Lens for CounterState would be:

let counterLens = Lens<TestState, CounterState> (get: { testState in return testState.counterState },
                                                 set: { (testState, counterState) -> TestState in
	var mutableTestState = testState
	mutableTestState.counterState = counterState
	return mutableTestState
    })

it's all about defining how to access the CounterState property (the get closure) of the State and how to mutate it (the set closure).

Mutator

A mutator is simply a structure that groups a Reducer and a Lens for a sub-State. Again for the CounterState:

let counterMutator = Mutator<TestState, CounterState>(lens: counterLens, reducer: counterReduce)

A Mutator has everything needed to know how to mutate the CounterState and how to set it to its parent State.

Let's put the pieces all together

After instantiating the Store, you have to register all the Mutators that will handle the State's sub-States.

let store = Store<TestState>(withState: TestState(counterState: .empty, userState: .loggedOut))
let counterMutator = Mutator<TestState, CounterState>(lens: counterLens, reducer: counterReduce)
let userMutator = Mutator<TestState, UserState>(lens: userLens, reducer: userReduce)

store.register(mutator: counterMutator)
store.register(mutator: userMutator)

And now lets mutate the state:

store.dispatch(action: AppAction.increase(increment: 10)).subscribe(onNext: { testState in
	print ("New State \(testState)")
}).disposed(by: self.disposeBag)

But wait, there's more ...

List of actions

Lately, Swift 4.1 has introduced conditional conformance. If you are not familiar with this concept: A Glance at conditional conformance.

Basically it allows to make a generic type conform to a protocol only if the associated inner type also conforms to this protocol.

For instance, RxReduce leverages this feature to make an Array of Actions be an Action to ! Doing so, it is perfectly OK to dispatch a list of actions to the Store like that:

let actions: [Action] = [AppAction.increase(increment: 10), AppAction.decrease(decrement: 5)]
store.dispatch(action: actions).subscribe ...

The actions declared in the array will be executed sequentially πŸ‘Œ .

Asynchronicity

Making an Array of Actions be an Action itself is neat, but since we're using Reactive Programming, RxReduxe also applies this technic to RxSwift Observables. It provides a very elegant way to dispatch an Observable<Action> to the Store (because Observable<Action> also conforms to Action), making asynchronous actions very simple.

let increaseAction = Observable<Int>.interval(1, scheduler: MainScheduler.instance).map { _ in AppAction.increase(increment: 1) }
store.dispatch(action: increaseAction).subscribe ...

This will dispatch a AppAction.increase Action every 1s and mutate the State accordingly.

If we want to compare RxReduce with Redux, this ability to execute async actions would be equivalent to the "Action Creator" concept.

For the record, we could even dispatch to the Store an Array of Observable<Action>, and it will be seen as an Action as well.

let increaseAction = Observable<Int>.interval(1, scheduler: MainScheduler.instance).map { _ in AppAction.increase(increment: 1) }
let decreaseAction = Observable<Int>.interval(1, scheduler: MainScheduler.instance).map { _ in AppAction.decrease(decrement: 1) }
let asyncActions: [Action] = [increaseAction, decreaseAction]
store.dispatch(action: asyncActions).subscribe ...

Conditional Conformance is a very powerful feature.

One more thing

The Store provides a way to "observe" the State mutations from anywhere. All you have to do is to subscribe to the "state" property:

store.state.subscribe(onNext: { appState in
	print (appState)
}).disposed(by: self.disposeBag)

Demo Application

A demo application is provided to illustrate the core mechanisms, such as asynchronicity, sub states and view state rendering.

Demo Application Demo Application

Tools and dependencies

RxReduce relies on:

  • SwiftLint for static code analysis (Github SwiftLint)
  • RxSwift to expose State and Actions as Observables your app and the Store can react to (Github RxSwift)
  • Reusable in the Demo App to ease the storyboard cutting into atomic ViewControllers (Github Reusable)
Comments
  • Replace 'Array' with 'ContiguousArray'

    Replace 'Array' with 'ContiguousArray'

    Description

    Reducer and Middleware are aliases of closure. closure is Reference Type as apple doc

    Array of reference types should use ContiguousArray. For ContiguousArray, it is described in Apple document as follows.

    If you need an array of reference types and the array does not need to be bridged to NSArray, use ContiguousArray instead of Array Ref: Advice: Use ContiguousArray with reference types when NSArray bridging is unnecessary

    using ContiguousArray may be more efficient and have more predictable performance than Array. Ref: ContiguousArray

    I replaced Array type of reducers and middlewares with ContiguousArray.

    Checklist

    • [x] this PR is based on develop or a 'develop related' branch
    • [x] the commits inside this PR have explicit commit messages
    • [x] the Jazzy documentation has been generated (if needed -> Jazzy RxReduce)
    enhancement 
    opened by shoheiyokoyama 5
  • Any recommended way to make getters?

    Any recommended way to make getters?

    Hi @twittemb,

    Thanks for this awesome lib :)

    I would like to get the current substate. Not via state() driver.

    The stateSubject is private. Any recommended way to obtain current state?

    Thanks, Derek

    opened by Derek-X-Wang 3
  • Documentation is not up to date with 0.7

    Documentation is not up to date with 0.7

    Hi,

    the README and the example project do not reflect the latest changes on the library, any plans on updating them?

    Also the changes seem quite "major" in between the version, in future do you expect more breaking changes, or do you feel like the implementation is stable now?

    Regards Daniel

    opened by dp-1a 1
  • Compiling RxReduce in Xcode 10.2 with Toolchain 4.2 fails

    Compiling RxReduce in Xcode 10.2 with Toolchain 4.2 fails

    RxReduce 0.9 build is failing with Swift Compiler Error, Command failed due to signal: Abort trap: 6

    This is reproduced as follows

    • Clone from master branch
    • carthage update
    • Download and install Swift Toolchain 4.2 (all 4.2 versions has the same build result)
    • Clean and Build the project
    • The build fails compiling Action.swift

    Result:

    Assertion failed: (hasVal), function operator*, file /Users/buildnode/jenkins/workspace/oss-swift-4.2-package-osx/llvm/include/llvm/ADT/Optional.h, line 145. 0 swift 0x000000010d6fb208 llvm::sys::PrintStackTrace(llvm::raw_ostream&) + 40 1 swift 0x000000010d6fa467 llvm::sys::RunSignalHandlers() + 39 2 swift 0x000000010d6fb882 SignalHandler(int) + 258 3 libsystem_platform.dylib 0x00007fff7254bb5d _sigtramp + 29 4 libsystem_platform.dylib 0x00000000000038b8 _sigtramp + 2376826232 5 libsystem_c.dylib 0x00007fff7240b6a6 abort + 127 6 libsystem_c.dylib 0x00007fff723d420d basename_r + 0 7 swift 0x000000010b163352 _ZN5swift23GenericSignatureBuilder19checkConstraintListINS_4TypeES2_EENS0_10ConstraintIT_EENS_12ArrayRefViewIS2_PNS_20GenericTypeParamTypeEL_ZNS_16staticCastHelperIS7_EEPS4_RKS2_ELb1EEERNSt3__16vectorIS5_NSE_9allocatorIS5_EEEEN4llvm12function_refIFbRKS5_EEENSL_IFNS0_18ConstraintRelationESN_EEENSK_8OptionalINS_4DiagIJjS2_T0_SV_EEEEENSU_IJS2_SV_EEENSU_IJjS2_SV_EEENSL_IFSV_RKS4_EEEb + 1522 8 swift 0x000000010b15d6e0 swift::GenericSignatureBuilder::checkSuperclassConstraints(swift::ArrayRefView<swift::Type, swift::GenericTypeParamType*, swift::GenericTypeParamType* swift::staticCastHelper<swift::GenericTypeParamType>(swift::Type const&), true>, swift::GenericSignatureBuilder::EquivalenceClass*) + 352 9 swift 0x000000010b15bd53 swift::GenericSignatureBuilder::finalize(swift::SourceLoc, swift::ArrayRefView<swift::Type, swift::GenericTypeParamType*, swift::GenericTypeParamType* swift::staticCastHelper<swift::GenericTypeParamType>(swift::Type const&), true>, bool) + 707 10 swift 0x000000010b1647cb swift::GenericSignatureBuilder::computeGenericSignature(swift::SourceLoc, bool, bool) && + 59 11 swift 0x000000010b1ac273 swift::RequirementEnvironment::RequirementEnvironment(swift::DeclContext*, swift::GenericSignature*, swift::ProtocolDecl*, swift::ClassDecl*, swift::ProtocolConformance*) + 2387 12 swift 0x000000010ae216dd swift::matchWitness(swift::TypeChecker&, swift::ProtocolDecl*, swift::ProtocolConformance*, swift::DeclContext*, swift::ValueDecl*, swift::ValueDecl*) + 365 13 swift 0x000000010ae22490 swift::WitnessChecker::findBestWitness(swift::ValueDecl*, bool*, swift::NormalProtocolConformance*, llvm::SmallVectorImpl<swift::RequirementMatch>&, unsigned int&, unsigned int&, bool&) + 656 14 swift 0x000000010ae2aa90 swift::ConformanceChecker::resolveWitnessViaLookup(swift::ValueDecl*) + 672 15 swift 0x000000010ae26e39 swift::ConformanceChecker::checkConformance(swift::MissingWitnessDiagnosisKind) + 713 16 swift 0x000000010ae2573a swift::MultiConformanceChecker::checkIndividualConformance(swift::NormalProtocolConformance*, bool) + 8346 17 swift 0x000000010ae234f8 swift::MultiConformanceChecker::checkAllConformances() + 136 18 swift 0x000000010ae2ed1d swift::TypeChecker::checkConformancesInContext(swift::DeclContext*, swift::IterableDeclContext*) + 3133 19 swift 0x000000010ae7d876 typeCheckFunctionsAndExternalDecls(swift::SourceFile&, swift::TypeChecker&) + 182 20 swift 0x000000010ae7e868 swift::performTypeChecking(swift::SourceFile&, swift::TopLevelContext&, swift::OptionSet<swift::TypeCheckingFlags, unsigned int>, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int) + 1832 21 swift 0x000000010aaa8fd2 swift::CompilerInstance::parseAndCheckTypes(swift::CompilerInstance::ImplicitImports const&) + 834 22 swift 0x000000010aaa8868 swift::CompilerInstance::performSema() + 472 23 swift 0x0000000109f2a5d2 performCompile(swift::CompilerInstance&, swift::CompilerInvocation&, llvm::ArrayRef<char const*>, int&, swift::FrontendObserver*, swift::UnifiedStatsReporter*) + 1410 24 swift 0x0000000109f29042 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 2946 25 swift 0x0000000109ee3e08 main + 1128 26 libdyld.dylib 0x00007fff723663d5 start + 1 27 libdyld.dylib 0x0000000000000058 start + 2378800260

    opened by jacob-zx-fs 0
  • value property on Store class is needed

    value property on Store class is needed

    Hi Currently store class has a property "state" which is Observable. Should it also provide a property "value" which data type is State?

    Best Regards Haojie

    opened by paulxi 0
  • How to support Collection state

    How to support Collection state

    Hi The demo app is very useful, and it helps me a lot to understand the api usage.

    Now I'm facing a problem when I use RxReduce into my project. Let me still use the demo app as example, if UX wants to change the current interaction behavior, instead of using model presentation to display movie detail view, the new app uses navigation presentation to display movie detail, and in the movie detail view, it has a new button 'Next', so without navigate back to movie list view, user can use it to navigate to the next movie detail view, then in this case the app creates 2 MovieDetailViewController.

    My assumption is AppState needs to have an array or dictionary of MovieDetailState, but I don't know how to implement Lens to support this functionality.

    Best Regards Paul

    opened by paulxi 0
  • How to register deep struct mutator?

    How to register deep struct mutator?

    Hi I have a question regarding to update deep struct.

    e.g. The root state is AppState, and it contains a struct property SubState, and the SubState contains a enum property SubSubState.

    I can declare 2 Mutator:

    1. Mutator<AppState, SubState> with Lens<AppState, SubState> and Reducer(state AppState, action: Action)->SubState
    2. Mutator<SubState, SubSubState> with Lens<SubState, SubSubState> and Reducer(state SubState, action: Action)->SubSubState

    But the Store register api only support case 1, any suggestion for updating deep struct?

    Best Regards Paul

    opened by paulxi 0
Releases(0.10)
Owner
RxSwift Community
RxSwift ecosystem projects
RxSwift Community
Predictable state container for Swift too

ReduxSwift ReduxSwift is a minimal Swift port of Redux, a popular JavaScript library for application state management. Functionality Centralized State

Lucas Sunsi Abreu 38 Oct 6, 2020
Redux for Swift - a predictable state container for Swift apps

Merge / deprecation announcement: ReduxKit and Swift-Flow have joined forces! The result is ReSwift. The nitty gritty: We decided to deprecate ReduxKi

null 613 Jan 3, 2023
Reactive Programming in Swift

Rx is a generic abstraction of computation expressed through Observable<Element> interface, which lets you broadcast and subscribe to values and other

ReactiveX 23.1k Jan 5, 2023
A Swift Reactive Programming Kit

ReactiveKit is a lightweight Swift framework for reactive and functional reactive programming that enables you to get into the reactive world today. T

Declarative Hub 1.2k Dec 29, 2022
Swift Reactive Programming.

ReactKit Swift Reactive Programming. How to install See Wiki page. Example For UI Demo, please see ReactKit/ReactKitCatalog. Key-Value Observing // cr

ReactKit 1.2k Nov 6, 2022
Simple and lightweight Functional Reactive Coding in Swift for the rest of us

The simplest Observable<T> implementation for Functional Reactive Programming you will ever find. This library does not use the term FRP (Functional R

Jens Ravens 1.1k Jan 3, 2023
Implementation of the repository pattern in Swift, using generics.

Store Simple, powerful and elegant implementation of the repository pattern, using generics. Why? ?? There are a couple of ways to implement the Repos

Narek Mailian 2 Aug 31, 2022
🌾 Harvest: Apple's Combine.framework + State Machine, inspired by Elm.

NOTE: This repository has been discontinued in favor of Actomaton. ?? Harvest Apple's Combine.framework (from iOS 13) + State Machine, inspired by Elm

Yasuhiro Inami 386 Dec 18, 2022
A barebone, thread-safe Redux-like container for Swift.

SwiftTinyRedux SwiftTinyRedux is a barebone, thread-safe Redux-like container for Swift. It features a minimal API and supports composable reducers. I

Valentin Radu 9 Nov 13, 2022
A reactive wrapper built around UIImagePickerController.

RxMediaPicker RxMediaPicker is a RxSwift wrapper built around UIImagePickerController consisting in a simple interface for common actions like picking

RxSwift Community 180 Apr 24, 2022
RxSwift reactive wrapper for view gestures

RxGesture Usage To run the example project, clone the repo, in the Example folder open RxGesture.xcworkspace. You might need to run pod install from t

RxSwift Community 1.3k Dec 30, 2022
Reactive Keyboard in iOS

RxKeyboard RxKeyboard provides a reactive way of observing keyboard frame changes. Forget about keyboard notifications. It also perfectly works with U

RxSwift Community 1.4k Dec 29, 2022
Reactive WebSockets

RxWebSocket Reactive extensions for websockets. A lightweight abstraction layer over Starscream to make it reactive. Installation RxWebSocket is avail

FlΓ‘vio Caetano 57 Jul 22, 2022
A powerful, minimal and composable architecture for building reactive iOS apps with SwiftUI or UIKit

SourceArchitecture A simple yet powerful framework for reactive programming with only a minimal optimized set of types. Sources are self-contained, hi

Daniel Hall 6 Nov 1, 2022
πŸ“¬ A lightweight implementation of an observable sequence that you can subscribe to.

Features Lightweight Observable is a simple implementation of an observable sequence that you can subscribe to. The framework is designed to be minima

Felix M. 133 Aug 17, 2022
πŸ€– RxSwift + State Machine, inspired by Redux and Elm.

RxAutomaton RxSwift port of ReactiveAutomaton (State Machine). Terminology Whenever the word "signal" or "(signal) producer" appears (derived from Rea

Yasuhiro Inami 719 Nov 19, 2022
Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux Introduction VueFlux is the architecture to manage state with unidi

Ryo Aoyama 324 Dec 17, 2022
🟣 Verge is a very tunable state-management engine on iOS App (UIKit / SwiftUI) and built-in ORM.

Verge.swift ?? An effective state management architecture for iOS - UIKit and also SwiftUI ?? _ An easier way to get unidirectional data flow _ _ Supp

VergeGroup 478 Dec 29, 2022
CMPSC475 Final Project, ArboretumID Application allows users to explore the Penn State Arboretum, identify plants and learn about the exhibits!

ArboretumID: CMPSC475 Final Project Taylan Unal (@taylanu) About ArboretumID ArboretumIdentifier (ArboretumID) is an app that enhances the Penn State

Taylan 1 Nov 27, 2021