Giter Site home page Giter Site logo

reswift / reactivereswift Goto Github PK

View Code? Open in Web Editor NEW
136.0 6.0 22.0 8.17 MB

Unidirectional Data Flow in Swift via FRP - Inspired by Elm

Home Page: http://reswift.github.io/ReactiveReSwift

License: MIT License

Ruby 6.89% Shell 10.21% Swift 81.80% Objective-C 1.10%
frp-library reactivereswift unidirectional-data-flow elm-architecture swift redux frp

reactivereswift's Introduction

ReactiveReSwift

Build Status License MIT Join the chat at https://gitter.im/ReactiveReSwift/Lobby

ReactiveReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift, derived from ReSwift. ReactiveReSwift helps you to separate three important concerns of your app's components:

  • State: in a ReactiveReSwift app the entire app state is explicitly stored in a data structure. This helps avoid complicated state management code, enables better debugging and has many, many more benefits...
  • Views: in a ReactiveReSwift app your views update when your state changes. Your views become simple visualizations of the current app state.
  • State Changes: in a ReactiveReSwift app you can only perform state changes through actions. Actions are small pieces of data that describe a state change. By drastically limiting the way state can be mutated, your app becomes easier to understand and it gets easier to work with many collaborators.

The ReactiveReSwift library is tiny - allowing users to dive into the code, understand every single line and hopefully contribute.

Check out our public gitter chat!

Table of Contents

About ReactiveReSwift

ReactiveReSwift relies on a few principles:

  • The Store stores your entire app state in the form of a single data structure. This state can only be modified by dispatching Actions to the store. Whenever the state in the store changes, the store will notify all observers.
  • Actions are a declarative way of describing a state change. Actions don't contain any code, they are consumed by the store and forwarded to reducers. Reducers will handle the actions by implementing a different state change for each action.
  • Reducers provide pure functions, that based on the current action and the current app state, create a new app state

For a very simple app, one that maintains a counter that can be increased and decreased, you can define the app state as following:

struct AppState {
  let counter: Int
}

You would also define two actions, one for increasing and one for decreasing the counter. For the simple actions in this example we can use an enum that conforms to action:

enum AppAction: Action {
    case increase
    case decrease
}

Your reducer needs to respond to these different actions, that can be done by switching over the value of action:

let appReducer: Reducer<AppState> = { action, state in
    switch action as? AppAction {
    case .increase?:
        return AppState(counter: state.counter + 1)
    case .decrease?:
        return AppState(counter: state.counter - 1)
    default:
        return state
    }
}

A single Reducer should only deal with a single field of the state struct. You can nest multiple reducers within your main reducer to provide separation of concerns.

In order to have a predictable app state, it is important that the reducer is always free of side effects, it receives the current app state and an action and returns the new app state.

To maintain our state and delegate the actions to the reducers, we need a store. Let's call it mainStore and define it as a global constant, for example in the app delegate file:

let initialState = AppState(counter: 0)

let mainStore = Store(
  reducer: appReducer,
  observable: ObservableProperty(initialState)
)

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
	[...]
}

Lastly, your view layer, in this case a view controller, needs to tie into this system by subscribing to store updates and emitting actions whenever the app state needs to be changed:

class CounterViewController: UIViewController {

  private let disposeBag = SubscriptionReferenceBag()
  @IBOutlet var counterLabel: UILabel!

  override func viewDidLoad() {
    disposeBag += mainStore.observable.subscribe { [weak self] state in
      self?.counterLabel.text = "\(state.counter)"
    }
  }

  @IBAction func increaseButtonTapped(sender: UIButton) {
    mainStore.dispatch(
      AppAction.increase
    )
  }

  @IBAction func decreaseButtonTapped(sender: UIButton) {
    mainStore.dispatch(
      AppAction.decrease
    )
  }

}

The mainStore.observable.subscribe block will be called by the ObservableStore whenever a new app state is available, this is where we need to adjust our view to reflect the latest app state.

Button taps result in dispatched actions that will be handled by the store and its reducers, resulting in a new app state.

This is a very basic example that only shows a subset of ReactiveReSwift's features, read the Getting Started Guide to see how you can build entire apps with this architecture. For a complete implementation of this example see the ReactiveCounterExample project.

You can also watch this talk on the motivation behind ReSwift.

Reactive Extensions

Here are some examples of what your code would look like if you were to leverage certain FRP libraries when writing your application.

Why ReactiveReSwift?

Model-View-Controller (MVC) is not a holistic application architecture. Typical Cocoa apps defer a lot of complexity to controllers since MVC doesn't offer other solutions for state management, one of the most complex issues in app development.

Apps built upon MVC often end up with a lot of complexity around state management and propagation. We need to use callbacks, delegations, Key-Value-Observation and notifications to pass information around in our apps and to ensure that all the relevant views have the latest state.

This approach involves a lot of manual steps and is thus error prone and doesn't scale well in complex code bases.

It also leads to code that is difficult to understand at a glance, since dependencies can be hidden deep inside of view controllers. Lastly, you mostly end up with inconsistent code, where each developer uses the state propagation procedure they personally prefer. You can circumvent this issue by style guides and code reviews but you cannot automatically verify the adherence to these guidelines.

ReactiveReSwift attempts to solve these problem by placing strong constraints on the way applications can be written. This reduces the room for programmer error and leads to applications that can be easily understood - by inspecting the application state data structure, the actions and the reducers.

Why "Reactive"?

A common design pattern with Redux and its derivates is to observe your store using functional reactive programming (FRP), which the user takes care of using very similar looking boilerplate, regardless of the FRP library they've chosen.

Instead of pushing that onto the user, and to encourage people to use FRP, ReactiveReSwift provides protocols to conform to so that the underlying Store can directly use the observables from your preferred library without subclassing.

ReactiveReSwift also comes with an extremely simple implementation of a functional reactive observable. This ObservableProperty type allows you to use ReactiveReSwift without any other FRP libraries and not lose any of the functionality provided by ReSwift. That said, we do still highly encourage you to use a functional reactive library with ReactiveReSwift.

Getting Started Guide

The documentation for ReactiveReSwift can be found here. To get an understanding of the core principles we recommend reading the brilliant redux documentation.

Installation

CocoaPods

You can install ReactiveReSwift via CocoaPods by adding it to your Podfile:

use_frameworks!

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'

pod 'ReactiveReSwift'

And run pod install.

Carthage

You can install ReactiveReSwift via Carthage by adding the following line to your Cartfile:

github "ReSwift/ReactiveReSwift"

Swift Package Manager

You can install ReactiveReSwift via Swift Package Manager by adding the following line to your Package.swift:

import PackageDescription

let package = Package(
    [...]
    dependencies: [
        .Package(url: "https://github.com/ReSwift/ReactiveReSwift.git", majorVersion: XYZ)
    ]
)

Roadmap

Swift 4

  • Change serialisation to use Codable and Decodable protocols provided by Swift 4.

Example Projects

Contributing

There's still a lot of work to do here! We would love to see you involved! You can find all the details on how to get started in the Contributing Guide.

Credits

  • Huge thanks to Evan Czaplicki for creating Elm, the first language to implement unidirectional data flow as a paradigm.
  • Thanks a lot to Dan Abramov for building Redux, many ideas in here were provided by his library.
  • Thanks a lot to Benjamin Encz for building ReSwift, the base from which this project was derived.

Get in touch

If you have any questions, you can find the core team on twitter:

We also have a public gitter chat!

reactivereswift's People

Contributors

agentk avatar ben-g avatar cuva avatar dcvz avatar delebedev avatar divinedominion avatar gitter-badger avatar hamaron avatar ivnsch avatar jlampa avatar jondwillis avatar juggernate avatar madhavajay avatar mikekavouras avatar mitsuse avatar netspencer avatar nickcheng avatar orta avatar qata avatar raheelahmad avatar richy486 avatar ryanccollins avatar sendyhalim avatar thomaspaulmann avatar tkersey avatar tomj avatar vadymmarkov avatar vfn avatar victorpimentel avatar vkotovv avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

reactivereswift's Issues

New release for swift 4?

Hi! I noticed, that in master there is code for swift 4, but there has been no tag for that. Is it just a matter of drafting a new release? Or is there any extra work that needs to be done? I could help with that

dispatch<S: StreamType>(_ stream: S) leaks hot observables

If we have a hot observable that produces actions, like let actionSource = Observable<Action> and we use store.dispatch(actionSource) there is no way to unsubscribe/dispose the observable causing a leak.

We need something function that dispatch the observable and return the SubscriptionReferenceType so we can unsubscribe when necessary.

As a workaround, I am using this extension function:


extension Store where ObservableProperty == Variable<AppState> {
    func dispatchWithDisposable<S: StreamType>(_ stream: S) where S.ValueType: Action -> SubscriptionReferenceType {
        return stream.subscribe({ [unowned self] action in
            self.dispatch(action)
        })
    }
} 

Although I think we need a more descriptive name or even remove/deprecate the old dispatch function in order to use the new one, and create an extension function for stream that add the subscription to the Store. Something like this:

store.dispatch(actionSource)
     .disposeByStore(store)

Combined reducer not possible?

How do you create a combined reducer?
Because the transform var of a reducer is set to internal it doesn't seem possible.
Or am I missing something?

screen shot 2017-02-15 at 17 49 33

Reducer incoming state not optional?

What is the reason behind the Reducer's incoming state not being optional?
According to Redux the incoming states can be undefined and then it is up to the reducers to supply an initial state.
http://redux.js.org/docs/basics/Reducers.html
It also seems that ReSwift itself has an optional state for their reducers.
https://github.com/ReSwift/ReSwift/blob/master/ReSwift/CoreTypes/Reducer.swift

If the state can be nil the initial state value can be supplied by the reducers individually.
Then you would not need to bother with creating an initial state object that has all its substates filled in.

Dispatch async action in MiddleWare

If you do an async operation in the MiddleWare you want to post the result as an action to the store.
But the store is not one of the incoming variables?
Or should you just use the dispatch method.
But that doesn't work currently as it is not marked as @escaped so you cannot use it to dispatch an action async.

Usage with ReSwiftRouter

As far as I understood, there is no way to it with ReSwiftRouter, as it requires ReSwift as dependency?

CocoaPods installation

Is there any reason why the library doesn't have a Podspec and is not released on CocoaPods yet?

UnitTest custom MiddleWare

The transform property of MiddleWare is set to internal.
This prevents you from unit testing the MiddleWare you wrote.
screen shot 2017-03-17 at 14 35 15
Could this be made public?

ReSwift vs ReactiveReSwift

Hi guys,

I just came across this Reactive version of ReSwift and the main question I had while reading the documentation is what are the main benefits of using it instead of the plain vanilla ReSwift?

Admittedly I have very little experience with FRP but as far as I can see the main difference between the two libraries is in the way we can subscribe to state updates? Are there any advantages between one over the other?

Thanks!
Rog

Combine support

Well Apple went and made their own reactive framework so it’s time to support it.

Action Creators

In the change log, it says:

Remove ActionCreator since this can easily be solved with Rx as a single value stream

Can you explain what you mean by that with a very small example?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.