Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

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
Appearance settings

Composable unidirectional data flow with ReactiveSwift.

License

NotificationsYou must be signed in to change notification settings

ReactiveCocoa/Loop

Repository files navigation

Unidirectional Reactive Architecture. This is aReactiveSwift counterpart ofRxFeedback.

Documentation

Motivation

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.

Core Concepts

State

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)}
Event

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}
Reducer

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)}}
Feedback

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))}}}

The Flow

  1. As you can see from the diagram above we always start with an initial state.
  2. Every change to theState will be then delivered to allFeedback loops that were added to the system.
  3. Feedback then 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.
  4. DispatchedEvent then goes to theReducer which applies it and returns a new value of theState.
  5. And then cycle starts all over (see 2).
Example
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)

Advantages

TODO

Acknowledgements

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

Stars

Watchers

Forks

Packages

No packages published

Contributors14


[8]ページ先頭

©2009-2025 Movatter.jp