ReduxStoreBase
open class ReduxStoreBase<ActionType, StateType> : ReduxStoreProtocol
πͺ ReduxStoreBase
is a base class that can be used to create the main store of an app, using the redux pattern.
A store should have a single input and a single output, being the input the method to handle actions dispatched by the
counterparts, and the output the state that can be observed by them. For that reason, a StoreType
protocol is nothing
but a composition of two other protocols: ActionHandler
and StateProvider
:
- as
ActionHandler
, which represents the store input, it’s gonna be able to receive and distribute action of a generic typeActionType
. Being an action handler means that anUIViewController
or SwiftUIView
can dispatch actions to it, such as.saveButtonTapped
,.didScrollToPosition(y)
,.viewDidLoad
or.queryTextFieldChanged(text)
. - as
StateProvider
, which represents the store output, it’s gonna be able to offer to the system a way to subscribe for updates on State. Being a state provider basically means that a store has astatePublisher
that is either aObservable<StateType>
,SignalProducer<StateType, Never>
orPublisher<StateType, Never>
depending on the reactive framework of your choice, so anUIViewController
can subscribe to state changes and react to them, or a SwiftUI View can use it as aObservedObject
.
This type of store will glue all the parts together and its responsibility is owning the main state, which means the only source-of-truth an app can have, besides of coordinating the sequence of operations that will be triggered once a new action arrives from views or middlewares. It’s highly recommended that your app should have only a single instance of this class and, directly or indirectly, all the other parts of your app will react to the state notifications sent by this instance.
That means that other types of store can act as a proxy to this one, but none of them should hold any state. For more
information on that please check StoreProjection
.
You can think of Store as a very heavy “Model” layer, completely detached from the Views, Controllers, Presenters etc.,
and where all the business logic stands. At a first sight it may look like transferring the “Massive” problem from a
layer to another, but the store actually won’t have any of this logic, only coordinate the multiple entities that do
that. These entities are Middleware
and Reducer
, and you can learn more about them in their own documentation.
The ReduxStoreBase
has a pipeline of middlewares and reducers. Upon an action arrival, which first is bottlenecked
into a serial queue, every middleware will have the chance to handle the action, and trigger side-effects in response.
These middlewares also have read-only access to the state at any point, and can dispatch new actions to the beginning
of the process at any point. Once all middlewares were informed about the action, now your reducers will have the
chance to act. The reducers can’t trigger side-effects or do any async operation, all they do is calculating a new
version of the app state from the old version of the app state plus the action. One-by-one the reducers will shape the
new state, accumulatively. When they are done, the store publishes the final state as the new one, and notifies all the
subscribers.
ββββββββββββββββββββββββββββββββββββββββββ
β β
β SwiftUI View / UIViewController β
β β
ββββββ¬ββββββββββββββββββββββββββββββββββββ
β β²
β β
β action notification
βββββββββββ β β
β βΌ β β β β β β β
β βββββββββββββββββββββββββββββ« State β£β
new actions β β Store Publisher ββ
from middleware β βΌ β β β β¬ β β βββ
β β βββββββββββββββββββββ ββ
β β β Middlewares β β ββ
ββββββββββ€βββββ βββββ ββββββ ββ
β ββ 1 βββΆβ 2 βββΆβ 3 ββββ β ββ
β ββββββ βββββ ββββββ β ββ
β ββββββββββββββββββ¬βββ ββββββ΄βββββ ββ
β β β β β ββ
β βββββββββββββββ β ββ State β ββ
β β βββββββββββββββββββββββ β ββ
β βΌ βΌ ββββββ²βββββ ββ
β βββββββββββββββββββββ β ββ
β β Reducers β β ββ
β ββββββ βββββ ββββββ β ββ
β ββ 1 βββΆβ 2 βββΆβ 3 ββ ββββββββββββ ββ
β ββββββ βββββ ββββββ state ββ
β βββββββββββββββββββββ mutation ββ
β ββ
βββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββ
By using this architecture, your model gets completely detached from the UIKit
/SwiftUI
world. And your UI gets
completely independent from side-effects, state mutations, threading, ownership, logic, logs, analytics and everything
other than UI. No more dependency injection for your views, they only need to know about the store (the main one or
a derived view store).
-
Pipeline to execute upon action arrival, containing all middlewares and reducers
Declaration
Swift
public let pipeline: ReduxPipelineWrapper<AnyMiddleware<ActionType, ActionType, StateType>>
-
State publisher which can be subscribed in order to be notified on every mutation
Declaration
Swift
public let statePublisher: UnfailablePublisherType<StateType>
-
Required initializer that configures the action handler pipeline and the state storage
Declaration
Swift
public init<M: MiddlewareProtocol>( subject: UnfailableReplayLastSubjectType<StateType>, reducer: Reducer<ActionType, StateType>, middleware: M, emitsValue: ShouldEmitValue<StateType> = .always ) where M.InputActionType == ActionType, M.InputActionType == M.OutputActionType, M.StateType == StateType
Parameters
subject
a reactive subject type that replays the last value, never fails and works on
StateType
elements. It should contain the initial state already.reducer
a reducer function wrapped in a monoid container of type
Reducer
, able to handle the state of the typeStateType
and actions of typeActionType
. Forreducer
composition, please use the diamond operator (<>
) and for reducers that understand only a sub-state part, use thelift
functions to elevate them to the same global state and global action type.middleware
a middleware pipeline, that can be any flat middleware or a
ComposedMiddleware
, as long as it’s able to handle the state of typeStateType
and actions of typeActionType
. Formiddleware
composition, please use the diamond operator (<>
) and for middlewares that understand only a sub-state part, use thelift
functions to elevate them to the same global state and global action type.