- Notifications
You must be signed in to change notification settings - Fork6
Composable unidirectional data flow with ReactiveSwift.
License
ReactiveCocoa/Loop
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Unidirectional Reactive Architecture. This is aReactiveSwift counterpart ofRxFeedback.
Requirements for iOS apps have become huge. Our code has to manage a lot of state e.g. server responses, cached data, UI state, routing etc. Some may say that Reactive Programming can help us a lot but, in the wrong hands, it can do even more harm to your code base.
The goal of this library is to provide a simple and intuitive approach to designing reactive state machines.
State is the single source of truth. It represents a state of your system and is usually a plain Swift type (which doesn't contain any ReactiveSwift primitives). Your state is immutable. The only way to transition from oneState to another is to emit anEvent.
structResults<T:JSONSerializable>{letpage:IntlettotalResults:IntlettotalPages:Intletresults:[T]staticfunc empty()->Results<T>{returnResults<T>(page:0, totalResults:0, totalPages:0, results:[])}}structContext{varbatch:Results<Movie>varmovies:[Movie]staticvarempty:Context{returnContext(batch:Results.empty(), movies:[])}}enumState{case initialcase paging(context:Context)case loadedPage(context:Context)case refreshing(context:Context)case refreshed(context:Context)case error(error:NSError, context:Context)case retry(context:Context)}
Represents all possible events that can happen in your system which can cause a transition to a newState.
enumEvent{case startLoadingNextPagecase response(Results<Movie>)case failed(NSError)case retry}
A Reducer is a pure function with a signature of(State, Event) -> State. WhileEvent represents an action that results in aState change, it's actually not whatcauses the change. AnEvent is just that, a representation of the intention to transition from one state to another. What actually causes theState to change, the embodiment of the correspondingEvent, is a Reducer. A Reducer is the only place where aState can be changed.
staticfunc reduce(state:State, event:Event)->State{switch event{case.startLoadingNextPage:return.paging(context: state.context)case.response(let batch):varcopy= state.context copy.batch= batch copy.movies+= batch.resultsreturn.loadedPage(context: copy)case.failed(let error):return.error(error: error, context: state.context)case.retry:return.retry(context: state.context)}}
WhileState represents where the system is at a given time,Event represents a trigger for state change, and aReducer is the pure function that changes the state depending on current state and type of event received, there is not as of yet any type to emit events given a particular current state. That's the job of theFeedback. It's essentially a "processing engine", listening to changes in the currentState and emitting the corresponding next events to take place. It's represented by a pure function with a signature ofSignal<State, NoError> -> Signal<Event, NoError>. Feedbacks don't directly mutate states. Instead, they only emit events which then cause states to change in reducers.
publicstructFeedback<State, Event>{publicletevents:(Scheduler,Signal<State,NoError>)->Signal<Event,NoError>}func loadNextFeedback(for nearBottomSignal:Signal<Void,NoError>)->Feedback<State,Event>{returnFeedback(predicate:{ !$0.paging}){ _inreturn nearBottomSignal.map{Event.startLoadingNextPage}}}func pagingFeedback()->Feedback<State,Event>{returnFeedback<State,Event>(skippingRepeated:{ $0.nextPage}){(nextPage)->SignalProducer<Event,NoError>inreturnURLSession.shared.fetchMovies(page: nextPage).map(Event.response).flatMapError{(error)->SignalProducer<Event,NoError>inreturnSignalProducer(value:Event.failed(error))}}}func retryFeedback(for retrySignal:Signal<Void,NoError>)->Feedback<State,Event>{returnFeedback<State,Event>(skippingRepeated:{ $0.lastError}){ _->Signal<Event,NoError>inreturn retrySignal.map{Event.retry}}}func retryPagingFeedback()->Feedback<State,Event>{returnFeedback<State,Event>(skippingRepeated:{ $0.retryPage}){(nextPage)->SignalProducer<Event,NoError>inreturnURLSession.shared.fetchMovies(page: nextPage).map(Event.response).flatMapError{(error)->SignalProducer<Event,NoError>inreturnSignalProducer(value:Event.failed(error))}}}
- As you can see from the diagram above we always start with an initial state.
- Every change to the
Statewill be then delivered to allFeedbackloops that were added to the system. Feedbackthen decides whether any action should be performed with a subset of theState(e.g calling API, observe UI events) by dispatching anEvent, or ignoring it by returningSignalProducer.empty.- Dispatched
Eventthen goes to theReducerwhich applies it and returns a new value of theState. - And then cycle starts all over (see 2).
letincrement=Feedback<Int,Event>{ _inreturnself.plusButton.reactive.controlEvents(.touchUpInside).map{ _inEvent.increment}}letdecrement=Feedback<Int,Event>{ _inreturnself.minusButton.reactive.controlEvents(.touchUpInside).map{ _inEvent.decrement}}letsystem=SignalProducer<Int,NoError>.system(initial:0, reduce:{(count, event)->Intinswitch event{case.increment:return count+1case.decrement:return count-1}}, feedbacks:[increment, decrement])label.reactive.text<~ system.map(String.init)
TODO
This is a community fork of theReactiveFeedback project (with the MIT license) from Babylon Health.
About
Composable unidirectional data flow with ReactiveSwift.
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.
Contributors14
Uh oh!
There was an error while loading.Please reload this page.


