Skip to content

Instantly share code, notes, and snippets.

@JustinGanzer
Created February 16, 2021 15:37
Show Gist options
  • Save JustinGanzer/3555927318953b9676ea215bb1df8bda to your computer and use it in GitHub Desktop.
Save JustinGanzer/3555927318953b9676ea215bb1df8bda to your computer and use it in GitHub Desktop.
A playground file showing odd behavior when creating a derived Redux-like store
import SwiftUI
import Combine
//REDUX
typealias Reducer<State, Action> = (inout State, Action) -> AnyPublisher<Action, Never>?
class Store<State, Action>: ObservableObject {
@Published private(set) var state: State
private let reducer: Reducer<State, Action>
var cancellables: Set<AnyCancellable> = []
init(
initialState: State,
reducer: @escaping Reducer<State, Action>
) {
self.state = initialState
self.reducer = reducer
}
deinit {
cancellables.removeAll()
}
func send(_ action: Action) {
guard let effect = reducer(&state, action) else {
return
}
effect
.receive(on: DispatchQueue.global(qos: .userInitiated))
.sink(receiveValue: send)
.store(in: &cancellables)
}
func derived<DerivedState: Equatable, ExtractedAction>(
deriveState: @escaping (State) -> DerivedState,
embedAction: @escaping (ExtractedAction) -> Action
) -> Store<DerivedState, ExtractedAction> {
let store = Store<DerivedState, ExtractedAction>(
initialState: deriveState(state),
reducer: { (_, action) -> AnyPublisher<ExtractedAction, Never>? in
self.send(embedAction(action))
return Empty().eraseToAnyPublisher()
}
)
$state
.map(deriveState)
//.removeDuplicates()
.receive(on: DispatchQueue.global(qos: .userInitiated))
.assign(to: \.state, on: store)
.store(in: &store.cancellables)
return store
}
}
class AppStore: Store<AppStore.State, AppStore.Event> {
struct State {
var profile: ProfileState
var settings: SettingsState
}
enum ProfileState {
case profileA
case profileB
}
enum SettingsState {
case settingA
case settingB
}
enum Event {
case profile(event: ProfileEvent)
case settings(event: SettingsEvent)
}
enum ProfileEvent {
case eventA
case eventB
}
enum SettingsEvent {
case eventA
case eventB
}
static func reduce(state: inout State, event: Event) -> AnyPublisher<Event, Never>? {
switch event {
case .profile(let event):
switch event {
case .eventA:
state.profile = .profileA
case .eventB:
state.profile = .profileB
}
case .settings(let event):
switch event {
case .eventA:
state.settings = .settingA
case .eventB:
state.settings = .settingB
}
}
return nil
}
}
let appStore = AppStore(initialState: .init(profile: .profileA, settings: .settingA), reducer: AppStore.reduce)
let derivedStore = appStore.derived(deriveState: \.settings, embedAction: AppStore.Event.settings)
///Only to see construction
//let derivedStoreVerbose: Store<AppStore.SettingsState, AppStore.SettingsEvent> = appStore.derived { (state) -> AppStore.SettingsState in
// return state.settings
//} embedAction: { (action) -> AppStore.Event in
// return AppStore.Event.settings(event: action)
//}
let mySubscription = derivedStore.$state.sink {
print("DerivedStore State == \($0)")
}
print("TestModel Settings------------------------")
appStore.send(.settings(event: .eventA))
appStore.send(.settings(event: .eventB))
appStore.send(.settings(event: .eventA))
sleep(3)
print("TestModel Profile-------------------------")
appStore.send(.profile(event: .eventA))
appStore.send(.profile(event: .eventB))
sleep(3)
print("DerivedModel Profile----------------------")
derivedStore.send(.eventA)
derivedStore.send(.eventB)
derivedStore.send(.eventA)
derivedStore.send(.eventB)
sleep(3)
print("Bug---------------------- DerivedStore filled with last setting event sent by AppStore")
print(derivedStore.state)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment