Skip to content

Instantly share code, notes, and snippets.

@cooler333
Last active June 18, 2022 10:18
Show Gist options
  • Save cooler333/4d2760054fe33615ac2a9de476af7ae4 to your computer and use it in GitHub Desktop.
Save cooler333/4d2760054fe33615ac2a9de476af7ae4 to your computer and use it in GitHub Desktop.
// Swift Playground example of Redux JS framework
import Foundation
import PlaygroundSupport
// MARK: Action
public protocol Action {
associatedtype T = Any
}
// MARK: Dispatch
typealias Dispatch<Action> = (_ action: Action) -> Action
// MARK: Reducer
typealias Reducer<S, Action> = (_ state: S, _ action: Action) -> S
// MARK: MiddlewareAPI
struct MiddlewareAPI<S, Action> {
let dispatch: (Action) -> Action
let getState: () -> S
}
// MARK: Middleware
typealias Middleware<S, Action> = (_ api: MiddlewareAPI<S, Action>) -> ((_ next: @escaping Dispatch<Action>) -> ((_ action: Action) -> Action))
// MARK: Store
struct Store<S, Action> {
let dispatch: Dispatch<Action>
let getState: () -> S
}
// MARK: StoreEnhancerStoreCreator
typealias StoreEnhancerStoreCreator<S, Action> = (_ reducer: @escaping Reducer<S, Action>, _ preloadState: S) -> Store<S, Action>
// MARK: Enhancer
typealias StoreEnhancer<S, Action> = (_ next: @escaping StoreEnhancerStoreCreator<S, Action>) -> StoreEnhancerStoreCreator<S, Action>
// MARK: createStore
func createStore<S, Action>(
reducer: @escaping Reducer<S, Action>,
preloadedState: S,
enhancer: StoreEnhancer<S, Action>? = nil
) -> Store<S, Action> {
if let enhancer = enhancer {
return enhancer { reducer, preloadState in
return createStore(reducer: reducer, preloadedState: preloadedState)
}(reducer, preloadedState)
}
let currentReducer = reducer
var currentState = preloadedState
func getState() -> S {
return currentState
}
func dispatch(action: Action) -> Action {
currentState = currentReducer(currentState, action)
return action
}
return Store<S, Action>(dispatch: dispatch, getState: getState)
}
// MARK: applyMiddleware
func applyMiddleware<S, Action>(middlewares: Middleware<S, Action>...) -> StoreEnhancer<S, Action> {
return { next -> StoreEnhancerStoreCreator in
return { reducer, preloadState -> Store<S, Action> in
let store = createStore(reducer: reducer, preloadedState: preloadState)
var dispatch: Dispatch<Action>!
let middlewareAPI: MiddlewareAPI = MiddlewareAPI(
dispatch: { (action: Action) in
dispatch(action)
},
getState: store.getState
)
let chain = middlewares.map { middleware in middleware(middlewareAPI) }
dispatch = compose(chain)(store.dispatch)
return Store<S, Action>(dispatch: dispatch, getState: store.getState)
}
}
}
// MARK: compose
func compose<Action>(_ funcs: [(@escaping Dispatch<Action>) -> ((Action) -> Action)]) -> (@escaping Dispatch<Action>) -> ((Action) -> Action) {
if funcs.count == 1 {
return funcs.first!
}
return funcs[1...].reduce(funcs.first!) { a, b in
return { action in
a(b(action))
}
}
}
// MARK: Test
enum FooAction: Action {
case foo(text: String)
case bar(count: Int)
case receive(value: String)
}
let reducer: Reducer<String, FooAction> = { state, action in
switch action {
case let .foo(text):
return state + ", " + text
case let .bar(count):
return state + ", " + "\(count)"
case let .receive(value):
return state + ", " + "async \(value)"
}
}
let loggerFoo: Middleware<String, FooAction> = { (store) in
return { next in
return { action in
print("[Foo] will dispatch action:", action)
let returnValue = next(action)
print("[Foo] state after dispatch:", store.getState())
return returnValue
}
}
}
let loggerBar: Middleware<String, FooAction> = { (store) in
return { next in
return { action in
print("[Bar] will dispatch action:", action)
let returnValue = next(action)
print("[Bar] state after dispatch:", store.getState())
return returnValue
}
}
}
let asyncMiddleware: Middleware<String, FooAction> = { (store) in
return { next in
return { action in
if case let .foo(value) = action {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
_ = store.dispatch(.receive(value: "valueFromAPI(\(value))"))
}
}
return next(action)
}
}
}
let store: Store<String, FooAction> = createStore(
reducer: reducer,
preloadedState: "initial string",
enhancer: applyMiddleware(middlewares: loggerFoo, loggerBar, asyncMiddleware)
)
store.dispatch(.foo(text: "one"))
store.dispatch(.bar(count: 2))
store.dispatch(.foo(text: "three"))
print(store.getState())
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
print("2 seconds gone")
print(store.getState())
}
PlaygroundPage.current.needsIndefiniteExecution = true
// Result:
//
// [Foo] will dispatch action: foo(text: "one")
// [Bar] will dispatch action: foo(text: "one")
// [Bar] state after dispatch: initial string, one
// [Foo] state after dispatch: initial string, one
// [Foo] will dispatch action: bar(count: 2)
// [Bar] will dispatch action: bar(count: 2)
// [Bar] state after dispatch: initial string, one, 2
// [Foo] state after dispatch: initial string, one, 2
// [Foo] will dispatch action: foo(text: "three")
// [Bar] will dispatch action: foo(text: "three")
// [Bar] state after dispatch: initial string, one, 2, three
// [Foo] state after dispatch: initial string, one, 2, three
// initial string, one, 2, three
// [Foo] will dispatch action: receive(value: "valueFromAPI(one)")
// [Bar] will dispatch action: receive(value: "valueFromAPI(one)")
// [Bar] state after dispatch: initial string, one, 2, three, async valueFromAPI(one)
// [Foo] state after dispatch: initial string, one, 2, three, async valueFromAPI(one)
// [Foo] will dispatch action: receive(value: "valueFromAPI(three)")
// [Bar] will dispatch action: receive(value: "valueFromAPI(three)")
// [Bar] state after dispatch: initial string, one, 2, three, async valueFromAPI(one), async valueFromAPI(three)
// [Foo] state after dispatch: initial string, one, 2, three, async valueFromAPI(one), async valueFromAPI(three)
// 2 seconds gone
// initial string, one, 2, three, async valueFromAPI(one), async valueFromAPI(three)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment