DEV Community

Valentin Knabel
Valentin Knabel

Posted on • Originally published at vknabel.com on

Swifducks: simple Redux store with multiple reducers

A simple example implementation of the Redux pattern with multiple reducers and listeners.

The name is derived from Swift + Redux = 🏎🦆

Originally written at 2018-06-10

Definitions

public final class Store<State, Action> {
    public typealias Reducer = (Action, inout State) -> Void

    public private(set) var state: State
    private var reducers: [Reducer] = []
    private var callbacks: [Weak<Listener<State>>] = []

    public init(initial state: State) {
        self.state = state
    }

    public func select(_ changes: @escaping (State) -> Void) -> Any {
        let subscription = Listener(on: changes)
        callbacks.append(Weak(subscription))
        return subscription
    }

    private func reduce(with reducer: @escaping Reducer) {
        reducers.append(reducer)
    }

    public func dispatch(_ action: Action) {
        state = reducers.reduce(into: state) { intermediate, reducer in
            reducer(action, &intermediate)
        }
        callbacks = callbacks
            .compactMap { $0.value }
            .map(Weak.init)
        callbacks.forEach { $0.value?.onChange(state) }
    }
}

public extension Store {
    convenience init(initial state: State, reducer: @escaping Reducer) {
        self.init(initial: state)
        reduce(with: reducer)
    }
}

internal struct Weak<A: AnyObject> {
    weak var value: A?

    init(_ value: A?) {
        self.value = value
    }
}

internal final class Listener<State> {
    var onChange: (State) -> Void

    init(on change: @escaping (State) -> Void) {
        onChange = change
    }
}
Enter fullscreen mode Exit fullscreen mode

Example Usage

enum IntAction {
    case increase
    case decrease
}

let root = Store<Int, IntAction>(initial: 0) { action, state in
    switch action {
    case .increase:
        state += 1
    case .decrease:
        state -= 1
    }
}

var sideEffect = -1
var listener: Any? = root.select {
    sideEffect = $0
}

root.dispatch(.increase)
root.state // will be 1
sideEffect // will be 1
listener = nil
root.dispatch(.decrease)
root.state // will be 0
sideEffect // will be 1
Enter fullscreen mode Exit fullscreen mode

Conclusion

Without explicit support for modularity, supporting multiple reducers as above is probably not needed. Instead some composability functions should be used.

On the other hand: it is obvious, that are implementing the Redux pattern and embracing unidirectional data flow is relatively easy.

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay