TestsTested | ✓ |
LangLanguage | SwiftSwift |
License | MIT |
ReleasedLast Release | Dec 2015 |
SPMSupports SPM | ✗ |
Maintained by Karl Bowden.
#13: @Ben-G has done an awesome job of developing an independent, very idiomatic, swift implementation of Redux as Swift-Flow.
After a short discussion it’s clear we all agree that collaboration and sharing of idea’s will be beneficial to both projects. Go check it out. Feedback on both projects would be appreciated.
ReduxKit is a swift implementation of the JavaScript Redux library by Dan Abramov and the React Community. ReduxKit stays as close as possible to Redux while bringing in Swift ways of doing things where appropriate.
A thorough walk through and description of the framework can be found at the official Redux repository: Redux.
It is currently implemented in a few swift apps and is frequently updated. Additions, middleware and help will be very much appreciated! So if you’re trying it out and have any suggestions - feel free to post an issue and I’ll be on it.
A more advanced Swift example of the original gist
import ReduxKit
/**
* This is a simple standard action. The only requirement is that an action complies to
* the Action protocol. The SimpleStandardAction contains a strongly typed rawPayload
* property. The protocol automatically assigns the rawPayload to the Actions payload
* property. This removes the necessity of type casting whenever working with actions in
* a reducer.
*
* There's also the StandardAction protocol, that requires the struct to have an
* initializer. This is required if the bindActionCreators helper is to be used.
*/
struct IncrementAction: SimpleStandardAction {
let meta: Any? = nil
let error: Bool = false
let rawPayload: Int = 1
}
struct DecrementAction: SimpleStandardAction {
let meta: Any? = nil
let error: Bool = false
let rawPayload: Int = 1
}
/**
* This is a simple reducer. It is a pure function that follows the syntax
* (state, action) -> state.
* It describes how an action transforms the previous state into the next state.
*
* Instead of using the actions.type property - as is done in the regular Redux framework
* we use the power of Swifts static typing to deduce the action.
*/
func counterReducer(previousState: Int?, action: Action) -> Int {
// Declare the reducers default value
let defaultValue = 0
var state = previousState ?? defaultValue
switch action {
case let action as IncrementAction:
return state + action.rawPayload
case let action as DecrementAction:
return state - action.rawPayload
default:
return state
}
}
/**
* The applications state. This should contain the state of the whole application.
* When building larger applications, you can optionally assign complex structs to
* properties on the AppState and handle them in the part of the application that
* uses them.
*/
struct AppState {
var count: Int!
}
/**
* Create the applications reducer. While we could create a combineReducer function
* we've currently chosen to allow reducers to be statically typed and accept
* static states - instead of Any - which currently forces us to define the
* application reducer as such. This could possibly be simplified with reflection.
*/
let applicationReducer = {(state: AppState? = nil, action: Action) -> AppState in
return AppState(
count: counterReducer(state?.count, action: action),
)
}
// Create application store. The second parameter is an optional default state.
let store = createStore(applicationReducer, nil)
let disposable = store.subscribe{ state in
print(state)
}
store.dispatch(IncrementAction())
// {counter: 1}
store.dispatch(IncrementAction())
// {counter: 2}
store.dispatch(DecrementAction())
// {counter: 1}
// Dispose of the subscriber after use.
disposable.dispose()
ReduxKit makes heavy use of Swift generics for simpler implementation and usage.
The only types used as generics are State
, Action
and PayloadType
. Of which only State
has no inference and will be commonly used. Because typealias
does not support generics this does cause the framework source to become harder to read. Example typealias
have been included privately in the source.
Once the root state type has been defined in your project, you may want to declare appropriate typealias
mapping to the JavaScript Redux types.
// These two are already exported by ReduxKit as they do not use the State generic
// typealias Dispatch = Action -> Action
// typealias DispatchTransformer = Dispatch -> Dispatch
struct State {} // Can be named anything you like, as long as it's consistent in the typealias declarations
// Underscores are used where needed to prevent clashes with exported protocols. Again, naming is up to you.
typealias Store = ReduxKit.Store<State>
typealias Reducer = (previousState: State?, action: Action) -> State
typealias Subscriber = (updatedState: State) -> ()
typealias MiddlewareApi = Store
typealias Middleware = MiddlewareApi -> DispatchTransformer
typealias StoreCreator = (reducer: Reducer, initialState: State?) -> Store
typealias StoreEnhancer = (StoreCreator) -> StoreCreator
typealias StateStream = ReduxKit.StateStream<State>
typealias StreamFactory = (initialState: State) -> StateStream
Typealias not supporting generics can been seen most in the applyMiddleware function and makes for the best example of how to expand the ReduxKit examples out.
// How the applyMiddleware function would ideally be declared
func applyMiddleware(middleware: [Middleware])
-> StoreEnhancer
// Expanding out (sans generic state):
func applyMiddleware(middleware: [MiddlewareApi -> DispatchTransformer])
-> StoreCreator
-> StoreCreator
func applyMiddleware(middleware: [Store -> DispatchTransformer])
-> ((Reducer, State?) -> Store)
-> ((Reducer, State?) -> Store)
func applyMiddleware(middleware: [Store -> DispatchTransformer])
-> (((State?, Action) -> State, State?) -> Store)
-> (((State?, Action) -> State, State?) -> Store)
// With the generic State
func applyMiddleware<State>(middleware: [(Store<State>) -> DispatchTransformer])
-> (((State?, Action) -> State, State?) -> Store<State>)
-> (((State?, Action) -> State, State?) -> Store<State>)
Simplified and actual public functions and types:
createStore(reducer: Reducer, state: State?) -> Store
createStore<State>(reducer: (State?, Action) -> State, state: State?) -> Store<State>
createStreamStore(streamFactory: StreamFactory, reducer: Reducer, state: State?) -> Store
createStreamStore<State>(
streamFactory: State -> StateStream<State> = createSimpleStream,
reducer: (State?, Action) -> State,
state: State?)
-> Store<State>
createStreamStore(streamFactory: StreamFactory) -> StoreCreator
createStreamStore<State>(streamFactory: State -> StateStream<State>)
-> (reducer: (State?, Action) -> State, state: State?)
-> Store<State>
protocol ReduxDisposable {
var disposed: Bool { get }
var dispose: () -> () { get }
}
struct SimpleReduxDisposable: ReduxDisposable {
init(disposed: () -> Bool, dispose: () -> ())
}
struct StateStream {
let dispatch: State -> ()
let subscribe: Subscriber -> ReduxDisposable
let getState: () -> State
init(dispatch: State -> (), subscribe: Subscriber -> ReduxDisposable, getState: () -> State)
}
struct StateStream<State> {
let dispatch: State -> ()
let subscribe: (State -> ()) -> ReduxDisposable
let getState: () -> State
init(dispatch: State -> (), subscribe: (State -> ()) -> ReduxDisposable, getState: () -> State)
}
protocol StoreType {
typealias State
var dispatch: Dispatch { get }
var subscribe: (Subscriber) -> ReduxDisposable { get }
var getState: () -> State { get }
init(dispatch: Dispatch, subscribe: (Subscriber) -> ReduxDisposable, getState: () -> State)
}
struct Store<State>: StoreType {
let dispatch: Dispatch
let subscribe: (Subscriber) -> ReduxDisposable
let getState: () -> State
var state: State
init(dispatch: Dispatch, subscribe: (Subscriber) -> ReduxDisposable, getState: () -> State)
}
func applyMiddleware(middleware: [Middleware]) -> StoreEnhancer
func applyMiddleware<State>(middleware: [Store<State> -> DispatchTransformer])
-> (((State?, Action) -> State, State?) -> Store<State>)
-> (((State?, Action) -> State, State?) -> Store<State>)
func bindActionCreators<Action where Action: StandardAction>(type: Action.Type, dispatch: Dispatch)
-> (payload: Action.PayloadType?)
-> ()
The ReduxKit Store is, at it’s heart, a subscribable stream of states. You are likely already familiar with the concept from reactive frameworks. The two are so similar in fact that you will more than likely want to use ReduxKit with you favourite reactive framework.
The StateStream type allow does exactly that. Combined with a StateStreamFactory it allows ReduxKit to use almost any existing reactive framework as the state stream.
There is a state stream included with ReduxKit. It is not advisable to use it for anything more than examples as it’s not thread or leak safe. The SimpleStateStream is just an array of subscribers.
ReduxKit has StateStream bindings available for:
TODO: Include examples and links to reactive stream providers.
I am hoping to get a solid influx of middleware up and running for ReduxKit so we can change the way we do iOS development in the future. I am therefore welcoming pull requests, feature requests and suggestions. I want this library to be the best it can be and I can only do that with the help of the rest of you.
The actual implementation of ReduxKit is fairly complete. More tooling is still required though.
MIT