Awesome
<img src="assets/RxState_Logo.png" alt="RxState" width="50" height="40"> RxState: Redux + RxSwift
RxState a predictable state container for Swift apps. It's a tiny library built on top of RxSwift and inspired by Redux that facilitates building Unidirectional Data Flow architecture.
Why Unidirectional Data Flow Architecture?
- Helps you manage state in a consistent and unified way that guaranty it’s always predictable (After all, state is the source of all evil and you wanna keep that evil in check).
- Limits the way app state can be mutated, which makes your app easier to understand.
- Makes your code easy to test.
- Enables faster debugging.
- It’s is entirely platform independent - you can easily use the same business logic and share it between apps for multiple platforms (iOS, tvOS, etc.).
Architecture Components
-
App State: A single immutable data structure. It includes the UI state, the navigation state and the state of your model layer.
-
Store:Contains the app state and notifies the
App State Observers
of theApp State
updates. -
Reducer: A pure function that takes the current app state and an
Action
as input, creates a newApp State
that reflects the changes described by theAction
, and returns the newApp State
. -
Action: Actions describe a state change. The only way to modified the
App State
is by dispatchingActions
to theStore
. -
Action Creators and Dispatchers: Creates
Action
s and dispatch them to the store. -
App State Observers: Observers the
App State
in theStore
to transform it to presentable data, write logs, etc. -
View: Presents the presentable data that was deriver from the
App State
and delivers the user's interactions to theAction Creators and Dispatchers
.
How it works?
<img src="assets/RxState-Pattern.jpeg" width="100%" height="100%">-
The
View/View Controller
sends events (TheView Model
's inputs) to theView Model
. -
The
View Model
creates anAction
from the received inputs and dispatch them to theStore
.
- The
View Model
can use a dedicatedAction Creator
s to createAction
s.Action Creator
s do can async work and, based on the results it gets, returns differentAction
s to theView Model
to dispatch.
-
The
Store
sends theApp State
and the receivedAction
to theReducer
. -
The
Reducer
receives the currentApp State
and the dispatchedAction
, computes and returns newApp State
. -
The
Store
sends the newApp State
to the subscribers.
- One of the subscribers could be a
Middleware
that logs theApp State
resulted from dispatching anAction
.
- The
View Model
receives the newApp State
, transform it presentable data, and send it to theView/View Controller
.
- The
View Model
can useTransformer
s to transform theApp State
to presentable data. This helps you reuse the transformation code in differentView Model
s.
- The
View/View Controller
render the UI to show the presentable data to the user.
How does RxState helps you build the Architecture?
RxState defines the main component for you:
-
Store
: Contains theApp State
in the form ofDriver<[SubstateType]>
. -
SubstateType
: A protocol that tags structs representing a substate. Ex.
struct TasksState: SubstateType {
var tasks: [Task]
var addingTask: Bool
}
You can add a Substate
s to the App State
by dispatching StoreAction.add(states: [SubstateType])
.
let tasksState = TasksState()
let action = StoreAction.add(states: [tasksState])
store.dispatch(action: action)
ActionType
: A protocol that tags anAction
. TheStore
has the followingAction
s:
public enum StoreAction: ActionType {
/// Adds substates to the application state.
case add(states: [SubstateType])
/// Removes all substates in the application state.
case reset
}
MainReducer
: A reducer used by theStore
's dispatch function to call the respective reducer based on the Action type.
let mainReducer: MainReducer = { (state: [SubstateType], action: ActionType) -> [SubstateType] in
// Copy the `App State`
var state: [SubstateType] = state
// Cast to a specific `Action`.
switch action {
case let action as TasksAction:
// Extract the `Substate`.
guard var (tasksStateIndex, tasksState) = state
.enumerated()
.first(where: { (_, substate: SubstateType) -> Bool in
return substate is Store.TasksState}
) as? (Int, Store.TasksState)
else {
fatalError("You need to register `TasksState` first")
}
// Reduce the `Substate` to get a new `Substate`.
tasksState = Store.reduce(state: tasksState, action: action)
// Replace the `Substate` in the `App State` with the new `Substate`.
state[tasksStateIndex] = tasksState as SubstateType
default:
fatalError("Unknown action type")
}
// Return the new `App State`
return state
}
MiddlewareType
: A protocol defining an object that can observe theApp State
and the last dispatchedAction
and does something with it like logging:
protocol LoggingMiddlewareType: Middleware, HasDisposeBag {}
final class LoggingMiddleware: LoggingMiddlewareType {
var disposeBag = DisposeBag()
func observe(currentStateLastAction: Driver<CurrentStateLastAction>) {
currentStateLastAction
.drive(
onNext: { (currentState: [SubstateType], lastAction: ActionType?) in
print(currentState)
print(lastAction)
}, onCompleted: nil, onDisposed: nil)
.disposed(by: disposeBag)
}
}
}
Dependencies
Requirements
- Swift 3
Installation
- Using CocoaPods:
pod 'RxState'
Demo
I have tried to make the demo app as comprehensive as possible. It currently runs on iOS and macOS. Notice how, because of the architecture, only the View/ View Controller layer needed to change in order to port the app from iOS to macOS.
Contributing
We would love to see you involved and feedback and contribution are greatly appreciated :) Checkout the Contributing Guide.
Influences and credits
Author
Nazih Shoura, shoura.nazeeh@gmail.com
License
This library belongs to RxSwiftCommunity.
RxState is available under the MIT license. See the LICENSE file for more info.