Skip to content

Instantly share code, notes, and snippets.

@mminer
Created November 13, 2017 02:32
Show Gist options
  • Save mminer/410e9c57918cee0b191511ed3d5e8343 to your computer and use it in GitHub Desktop.
Save mminer/410e9c57918cee0b191511ed3d5e8343 to your computer and use it in GitHub Desktop.
Example of a Redux-esque store powered by RxSwift.
typealias Reducer<StateType, ActionType> = (_ state: StateType, _ action: ActionType) -> StateType
import RxSwift
final class Store<StateType, ActionType> {
private(set) var state: StateType
private let reducer: Reducer<StateType, ActionType>
private let subject: BehaviorSubject<StateType>
init(reducer: @escaping Reducer<StateType, ActionType>, state: StateType) {
self.reducer = reducer
self.state = state
self.subject = BehaviorSubject(value: state)
}
deinit {
subject.onCompleted()
}
/// Dispatches an action to trigger a state change.
func dispatch(_ action: ActionType) {
state = reducer(state, action)
subject.onNext(state)
}
/// Subscribes to changes to the state.
func observe<T: Equatable>(_ keyPath: KeyPath<StateType, T>) -> Observable<T> {
return subject
.map { $0[keyPath: keyPath] }
.distinctUntilChanged()
}
/// Subscribes to changes to the state.
func observe<T: Equatable>(_ keyPath: KeyPath<StateType, [T]>) -> Observable<[T]> {
return subject
.map { $0[keyPath: keyPath] }
.distinctUntilChanged(==)
}
}
enum Action {
case search(query: String)
case signIn(name: String, email: String)
case signOut
}
struct State {
let name: String
let email: String
let searchQuery: String
var isSignedIn: Bool {
return name.isEmpty && email.isEmpty
}
static let initial = State(name: "", email: "", searchQuery: "")
}
struct Reducers {
static func rootReducer(_ state: State, _ action: Action) -> State {
return State(
name: name(state, action),
email: email(state, action),
searchQuery: searchQuery(state, action)
)
}
private static func name(_ state: State, _ action: Action) -> String {
switch action {
case .signIn(let name)
return name
case .signOut:
return ""
default:
return state.name
}
}
private static func email(_ state: State, _ action: Action) -> String {
switch action {
case .signIn(_, let email)
return email
case .signOut:
return ""
default:
return state.email
}
}
private static func searchQuery(_ state: State, _ action: Action) -> String {
switch action {
case .search(let query)
return query
default:
return state.query
}
}
}
let store = Store(reducer: Reducers.root, state: .initial)
store.observe(\.name)
.subscribe(onNext: { name in
// Update UI
})
store.observe(\.searchQuery)
.filter { !$0.isEmpty }
.debounce(300)
.flatMapLatest { searchQuery in
// Perform search
})
.subscribe(onNext: { searchResults in
// Update UI
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment