Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

💎 Redux like architecture for SwiftUI

License

NotificationsYou must be signed in to change notification settings

gre4ixin/ReduxUI

Repository files navigation

logo

PlatformSwiftLicensePlatform Version

Simple Architecture like Redux

Installation

SPM

dependencies:[.package(url:"https://github.com/gre4ixin/ReduxUI.git",.upToNextMinor(from:"1.0.0"))]

Usage

import ReduxUIclassSomeCoordinator:Coordinator{func perform(_ route:SomeRoute){}}enumSomeRoute:RouteType{}enumAppAction:AnyAction{case increasecase decrease}structAppState:AnyState{varcounter:Int=0}classAppReducer:Reducer{typealiasAction=AppActionfunc reduce(_ state:inoutAppState, action:AppAction, performRoute:@escaping((_ route:SomeRoute)->Void)){switch action{case.increase:            state.counter+=1case.decrease:            state.counter-=1}}}classContentView:View{@EnvironmentObjectvarstore:Store<AppState,AppAction,SomeRouter>varbody:someView{VSTack{Text(store.state.counter)Button{                store.dispatch(.increase)} label:{Text("increment")}Button{                store.dispatch(.decrease)} label:{Text("decrement")}}}}classAppModuleAssembly{func build()->someView{letreducer=AppReducer().eraseToAnyReducer()letcoordinator=SomeCoordinator().eraseToAnyCoordinator()letstore=Store(initialState:AppState(), coordinator: coordinator, reducer: reducer)letview=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 return
AnyPublisher<MiddlewareAction,Never>
For example create simple project who fetch users fromhttps://jsonplaceholder.typicode.com/users.

Create DTO (Decode to object) model

structUserDTO:Decodable,Equatable,Identifiable{letid:Intletname:Stringletusername:Stringletphone:String}

Equatable protocol for our state,Identifiable forForEach generate view in SwiftUI View.

Simple network request without error checking
import Foundationimport CombineprotocolNetworkWrapperInterface{func request<D:Decodable>(path:URL, decode:D.Type)->AnyPublisher<D,NetworkError>}structNetworkError:Error{letresponse:URLResponse?leterror:Error?}classNetworkWrapper:NetworkWrapperInterface{func request<D:Decodable>(path:URL, decode:D.Type)->AnyPublisher<D,NetworkError>{returnDeferred{Future<D,NetworkError>{ promiseinletrequest=URLRequest(url: path)URLSession.shared.dataTask(with: request){[weak self] data, response, erroringuardlet _=selfelse{return}iflet _error= error{promise(.failure(NetworkError(response: response, error: _error)))}guardlet 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
enumAppAction:AnyAction{case fetchcase isLoadingcase loadingEndedcase updateUsers([UserDTO])case error(message:String)}structAppState:AnyState{varusers:[UserDTO]=[]varisLoading=falsevarerrorMessage=""}classAppReducer:Reducer{typealiasAction=AppActionfunc reduce(_ state:inoutAppState, action:AppAction, performRoute:@escaping((RouteWrapperAction)->Void)){switch action{case.fetch:            state.isLoading=true            state.errorMessage=""case.isLoading:            state.isLoading=truecase.loadingEnded:            state.isLoading=falsecase.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.
classAppMiddleware:Middleware{typealiasState=AppStatetypealiasAction=AppActiontypealiasRouter=RouteWrapperActionletnetworkWrapper:NetworkWrapperInterfacevarcancelabels=CombineBag()init(networkWrapper:NetworkWrapperInterface){self.networkWrapper= networkWrapper}func execute(_ state:AppState, action:AppAction)->AnyPublisher<MiddlewareAction<AppAction,RouteWrapperAction>,Never>?{switch action{case.fetch:returnDeferred{Future<MiddlewareAction<AppAction,RouteWrapperAction>,Never>{[weak self] promiseinguardlet self=selfelse{return}self.networkWrapper.request(path:URL(string:"https://jsonplaceholder.typicode.com/users")!, decode:[UserDTO].self).sink{ resultinswitch result{case.finished:breakcase.failure(let error):promise(.success(.performAction(.error(message:"Something went wrong!"))))}} receiveValue:{ dtoinpromise(.success(.performAction(.updateUsers(dto))))}.store(in:&self.cancelabels)}}.eraseToAnyPublisher()default:returnnil}}}

Content View

@EnvironmentObjectvarstore:Store<AppState,AppAction,RouteWrapperAction>varbody:someView{VStack{ScrollView{ForEach(store.state.users){ userinHStack{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.

publicprotocolDeferredAction{associatedtypeAction:AnyActionfunc 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.


[8]ページ先頭

©2009-2025 Movatter.jp