conversations-classic-ios/old/AppCore/AppStore.swift
2024-08-11 13:09:29 +02:00

103 lines
4.1 KiB
Swift

// This file declare global state object for whole app
// and reducers/actions/middleware types. Core of app.
import Combine
import Foundation
typealias Stateable = Codable & Equatable
typealias AppStore = Store<AppState, AppAction>
typealias Reducer<State: Stateable, Action: Codable> = (inout State, Action) -> Void
typealias Middleware<State: Stateable, Action: Codable> = (State, Action) -> AnyPublisher<Action, Never>?
final class Store<State: Stateable, Action: Codable>: ObservableObject {
@Published private(set) var state: State
// Serial queue for performing any actions sequentially
private let serialQueue = DispatchQueue(label: "im.narayana.conversations.classic.serial.queue", qos: .userInteractive)
private let middlewareQueue = DispatchQueue(label: "im.narayana.conversations.classic.middleware.queue", qos: .default, attributes: .concurrent)
private let reducer: Reducer<State, Action>
private let middlewares: [Middleware<State, Action>]
private var middlewareCancellables: Set<AnyCancellable> = []
// Init
init(
initialState: State,
reducer: @escaping Reducer<State, Action>,
middlewares: [Middleware<State, Action>] = []
) {
state = initialState
self.reducer = reducer
self.middlewares = middlewares
}
// Run reducers/middlewares
func dispatch(_ action: Action) {
if !Thread.isMainThread {
print("❌WARNING!: AppStore.dispatch should be called from the main thread")
}
serialQueue.sync { [weak self] in
guard let wSelf = self else { return }
let newState = wSelf.dispatch(wSelf.state, action)
wSelf.state = newState
}
}
private func dispatch(_ currentState: State, _ action: Action) -> State {
// Do reducing
var startTime = CFAbsoluteTimeGetCurrent()
var newState = currentState
reducer(&newState, action)
var timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
if timeElapsed > 0.05 {
#if DEBUG
print(
"""
--
(Ignore this warning ONLY in case, when execution is paused by your breakpoint)
🕐Execution time: \(timeElapsed)
❌WARNING! Some reducers work too long! It will lead to issues in production build!
Because of execution each action is synchronous the any stuck will reduce performance dramatically.
Probably you need check which part of reducer/middleware should be async (wrapped with Futures, as example)
--
"""
)
#else
#endif
}
// Dispatch all middleware functions
for middleware in middlewares {
guard let middleware = middleware(newState, action) else {
break
}
startTime = CFAbsoluteTimeGetCurrent()
middleware
.subscribe(on: middlewareQueue)
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] action in
self?.dispatch(action)
})
.store(in: &middlewareCancellables)
timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
if timeElapsed > 0.05 {
#if DEBUG
print(
"""
--
(Ignore this warning ONLY in case, when execution is paused by your breakpoint)
🕐Execution time: \(timeElapsed)
❌WARNING! Middleware work too long! It will lead to issues in production build!
Because of execution each action is synchronous the any stuck will reduce performance dramatically.
Probably you need check which part of reducer/middleware should be async (wrapped with Futures, as example)
--
"""
)
#else
#endif
}
}
return newState
}
}