Skip to content

Instantly share code, notes, and snippets.

@Arthraim
Last active January 23, 2018 03:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Arthraim/e9b142118fd2dc3318320d9536944ca4 to your computer and use it in GitHub Desktop.
Save Arthraim/e9b142118fd2dc3318320d9536944ca4 to your computer and use it in GitHub Desktop.
An extremely easy implementation of redux in Swift 4
//: An extremely easy implementation of redux in Swift 4
// !!!!
// THIS IMPLEMENTATION HAS BEEN MOVED TO https://github.com/Arthraim/redux.swift
// !!!!
typealias Reducer<S, A> = (_ state: S, _ action: A) -> S
typealias Listener = () -> Void
class Store<S, A> {
var currentReducer: Reducer<S, A>
var currentState: S
var currentListeners: [Int:Listener]
var nextListeners: [Int:Listener]
var isDispatching: Bool
init(_ reducer: @escaping Reducer<S, A>, preloadedState: S){
currentReducer = reducer
currentState = preloadedState
currentListeners = [:]
nextListeners = currentListeners
isDispatching = false
}
func getState() -> S {
guard !isDispatching else {
fatalError(
"You may not call store.getState() while the reducer is executing. " +
"The reducer has already received the state as an argument. " +
"Pass it down from the top reducer instead of reading it from the store."
)
}
return currentState
}
func subscribe(listener: @escaping Listener) -> () -> Void {
guard !isDispatching else {
fatalError(
"You may not call store.subscribe() while the reducer is executing. " +
"If you would like to be notified after the store has been updated, subscribe from a " +
"component and invoke store.getState() in the callback to access the latest state. "
)
}
var isSubscribed = true
let key = Int(arc4random_uniform(UInt32(6)))
nextListeners.updateValue(listener, forKey: key)
return {
guard isSubscribed else { return }
guard !self.isDispatching else {
fatalError("You may not unsubscribe from a store listener while the reducer is executing. ")
}
isSubscribed = false
self.nextListeners.removeValue(forKey: key)
}
}
func dispatch(action: A) -> A {
guard !isDispatching else {
fatalError("Reducers may not dispatch actions.")
}
isDispatching = true
currentState = currentReducer(currentState, action)
isDispatching = false
currentListeners = nextListeners
for listener in currentListeners.values {
listener()
}
return action
}
}
enum CounterAction {
case inc(num: Int)
case dec(num: Int)
}
typealias CounterState = Int
func counter(_ state: CounterState, _ action: CounterAction) -> CounterState {
switch action {
case .inc(let i):
return state + i
case .dec(let i):
return state - i
}
}
let store = Store(counter, preloadedState: 0)
func render() {
let state = store.getState()
print(state)
}
let unsubscribe = store.subscribe(listener: render)
store.dispatch(action: .inc(num: 1))
store.dispatch(action: .inc(num: 1))
store.dispatch(action: .inc(num: 2))
store.dispatch(action: .inc(num: 1))
store.dispatch(action: .dec(num: 1))
store.dispatch(action: .dec(num: 3))
unsubscribe()
store.dispatch(action: .inc(num: 3))
@luosky
Copy link

luosky commented Jan 22, 2018

    func unsubscribe(_ index: Int) {
        guard isSubscribed else { return }
        guard !isDispatching else {
            fatalError("You may not unsubscribe from a store listener while the reducer is executing. ")
        }

        isSubscribed = false

        nextListeners.remove(at: index)
    }

isSubscribed 应该要 nextListeners.count 为 0 时才设为 false?
而因此其实也不需要一个变量来存储 isSubscribed,直接用一个方法返回 nextListeners.count != 0 即可?

@luosky
Copy link

luosky commented Jan 22, 2018

另外通过记 index 来 unsubscribe 也比较危险,如果中间的 listener unsubscribe 了,后面的 index 应该就变了?
理想情况可能还是 Listener 能够传入自己来找到自己?

@Arthraim
Copy link
Author

@luosky 更新啦,解决啦

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment