Simple Architecture like Redux dependencies: [ . package ( url: " https://github.com/gre4ixin/ReduxUI.git " , . upToNextMinor( from: " 1.0.0 " ) ) ] import ReduxUIclass SomeCoordinator : Coordinator { func perform( _ route: SomeRoute ) { } } enum SomeRoute : RouteType { } enum AppAction : AnyAction { case increasecase decrease} struct AppState : AnyState { var counter : Int = 0 } class AppReducer : Reducer { typealias Action = AppAction func reduce( _ state: inout AppState , action: AppAction , performRoute: @escaping ( ( _ route: SomeRoute ) -> Void ) ) { switch action{ case . increase: state. counter+= 1 case . decrease: state. counter-= 1 } } } class ContentView : View { @EnvironmentObject var store : Store < AppState , AppAction , SomeRouter > var body : some View { VSTack { Text ( store. state. counter) Button { store. dispatch ( . increase) } label: { Text ( " increment " ) } Button { store. dispatch ( . decrease) } label: { Text ( " decrement " ) } } } } class AppModuleAssembly { func build( ) -> some View { let reducer = AppReducer ( ) . eraseToAnyReducer ( ) let coordinator = SomeCoordinator ( ) . eraseToAnyCoordinator ( ) let store = Store ( initialState: AppState ( ) , coordinator: coordinator, reducer: reducer) let view = ContentView ( ) . environmentObject ( store) return view} } That was very simple example, in real life you have to use network request, action in app state changes and many other features. In these cases you can useMiddleware.
Middlewares calls after reducer function and returnAnyPublisher < MiddlewareAction , Never > For example create simple project who fetch users fromhttps://jsonplaceholder.typicode.com/users. Create DTO (Decode to object) model
struct UserDTO : Decodable , Equatable , Identifiable { let id : Int let name : String let username : String let phone : String } Equatable protocol for our state,Identifiable forForEach generate view in SwiftUI View.
Simple network request without error checking import Foundationimport Combineprotocol NetworkWrapperInterface { func request< D: Decodable > ( path: URL , decode: D . Type ) -> AnyPublisher < D , NetworkError > } struct NetworkError : Error { let response : URLResponse ? let error : Error ? } class NetworkWrapper : NetworkWrapperInterface { func request< D: Decodable > ( path: URL , decode: D . Type ) -> AnyPublisher < D , NetworkError > { return Deferred { Future < D , NetworkError > { promisein let request = URLRequest ( url: path) URLSession . shared. dataTask ( with: request) { [ weak self] data, response, errorin guard let _= self else { return } if let _error= error{ promise ( . failure( NetworkError ( response: response, error: _error) ) ) } guard let unwrapData= data, let json= try ? JSONDecoder ( ) . decode ( decode, from: unwrapData) else { promise ( . failure( NetworkError ( response: response, error: error) ) ) return } promise ( . success( json) ) } . resume ( ) } } . eraseToAnyPublisher ( ) } } MakeState,Action andReducer enum AppAction : AnyAction { case fetchcase isLoadingcase loadingEndedcase updateUsers( [ UserDTO ] ) case error( message: String ) } struct AppState : AnyState { var users : [ UserDTO ] = [ ] var isLoading = false var errorMessage = " " } class AppReducer : Reducer { typealias Action = AppAction func reduce( _ state: inout AppState , action: AppAction , performRoute: @escaping ( ( RouteWrapperAction ) -> Void ) ) { switch action{ case . fetch: state. isLoading= true state. errorMessage= " " case . isLoading: state. isLoading= true case . loadingEnded: state. isLoading= false case . updateUsers( let users) : state. users= users state. isLoading= false state. errorMessage= " " case . error( let message) : state. errorMessage= message} } } Middleware for make network request and returnusers DTO. class AppMiddleware : Middleware { typealias State = AppState typealias Action = AppAction typealias Router = RouteWrapperAction let networkWrapper : NetworkWrapperInterface var cancelabels = CombineBag ( ) init ( networkWrapper: NetworkWrapperInterface ) { self . networkWrapper= networkWrapper} func execute( _ state: AppState , action: AppAction ) -> AnyPublisher < MiddlewareAction < AppAction , RouteWrapperAction > , Never > ? { switch action{ case . fetch: return Deferred { Future < MiddlewareAction < AppAction , RouteWrapperAction > , Never > { [ weak self] promisein guard let self= self else { return } self . networkWrapper. request ( path: URL ( string: " https://jsonplaceholder.typicode.com/users " ) !, decode: [ UserDTO ] . self) . sink { resultin switch result{ case . finished: break case . failure( let error) : promise ( . success( . performAction( . error( message: " Something went wrong! " ) ) ) ) } } receiveValue: { dtoin promise ( . success( . performAction( . updateUsers( dto) ) ) ) } . store ( in: & self . cancelabels) } } . eraseToAnyPublisher ( ) default : return nil } } } Content View
@EnvironmentObject var store : Store < AppState , AppAction , RouteWrapperAction > var body : some View { VStack { ScrollView { ForEach ( store. state. users) { userin HStack { VStack { Text ( user. name) . padding ( . leading, 16 ) Text ( user. phone) . padding ( . leading, 16 ) } Spacer ( ) } Divider ( ) } } Spacer ( ) if store. state. isLoading{ Text ( " Loading " ) } if !store. state. errorMessage. isEmpty{ Text ( LocalizedStringKey ( store. state. errorMessage) ) } Button { store. dispatch ( . fetch) } label: { Text ( " fetch users " ) } } } When reducer ended his job with action, our store check all added middlewares for somePublishers for curentAction, if Publisher not nil,Store runing that Publisher.
You can return action for reducer and change some data, return action for routing, return.multiple actions.
case multiple( [ MiddlewareAction < A , R > ] ) You can returnDeferred Action. public protocol DeferredAction { associatedtype Action : AnyAction func observe( ) -> AnyPublisher < Action , Never > ? func eraseToAnyDeferredAction( ) -> AnyDeferredAction < A > } If you want route to Authorization, when your Session Provider send event about dead you session, you can use itaction. All you need that conform to protocolDeferredAction youclass/struct and erase it toAnyDeferredAction with genericAction.