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
.