Coder Social home page Coder Social logo

reswift / reswift Goto Github PK

View Code? Open in Web Editor NEW
7.5K 146.0 517.0 9.38 MB

Unidirectional Data Flow in Swift - Inspired by Redux

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

License: MIT License

Swift 89.98% Objective-C 0.52% Shell 5.22% Ruby 4.28%
reswift swift unidirectional-data-flow redux

reswift's Introduction

ReSwift

Build Status Code coverage status CocoaPods Compatible Platform support License MIT Reviewed by Hound

Introduction

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

  • State: in a ReSwift 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 ReSwift app your views update when your state changes. Your views become simple visualizations of the current app state.
  • State Changes: in a ReSwift 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 ReSwift library is tiny - allowing users to dive into the code, understand every single line and hopefully contribute.

ReSwift is quickly growing beyond the core library, providing experimental extensions for routing and time traveling through past app states!

Excited? So are we πŸŽ‰

Check out our public gitter chat!

Table of Contents

About ReSwift

ReSwift 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, that maintains a counter that can be increased and decreased, you can define the app state as following:

struct AppState {
    var counter: Int = 0
}

You would also define two actions, one for increasing and one for decreasing the counter. In the Getting Started Guide you can find out how to construct complex actions. For the simple actions in this example we can define empty structs that conform to action:

struct CounterActionIncrease: Action {}
struct CounterActionDecrease: Action {}

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

func counterReducer(action: Action, state: AppState?) -> AppState {
    var state = state ?? AppState()

    switch action {
    case _ as CounterActionIncrease:
        state.counter += 1
    case _ as CounterActionDecrease:
        state.counter -= 1
    default:
        break
    }

    return state
}

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 mainStore = Store<AppState>(
	reducer: counterReducer,
	state: nil
)

@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, StoreSubscriber {

    @IBOutlet var counterLabel: UILabel!

    override func viewWillAppear(_ animated: Bool) {
        mainStore.subscribe(self)
    }

    override func viewWillDisappear(_ animated: Bool) {
        mainStore.unsubscribe(self)
    }

    func newState(state: AppState) {
        counterLabel.text = "\(state.counter)"
    }

    @IBAction func increaseButtonTapped(_ sender: UIButton) {
        mainStore.dispatch(
            CounterActionIncrease()
        )
    }

    @IBAction func decreaseButtonTapped(_ sender: UIButton) {
        mainStore.dispatch(
            CounterActionDecrease()
        )
    }

}

The newState method will be called by the Store 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 ReSwift'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 CounterExample project.

Create a subscription of several substates combined

Just create a struct representing the data model needed in the subscriber class, with a constructor that takes the whole app state as a param. Consider this constructor as a mapper/selector from the app state to the subscriber state. Being MySubState a struct and conforming to Equatable, ReSwift (by default) will not notify the subscriber if the computed output hasn't changed. Also, Swift will be able to infer the type of the subscription.

struct MySubState: Equatable {
    // Combined substate derived from the app state.
    
    init(state: AppState) {
        // Compute here the substate needed.
    }
}
store.subscribe(self) { $0.select(MySubState.init) }
    
func newState(state: MySubState) {
    // Profit!
}

Why ReSwift?

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.

ReSwift 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.

This architecture provides further benefits beyond improving your code base:

  • Stores, Reducers, Actions and extensions such as ReSwift Router are entirely platform independent - you can easily use the same business logic and share it between apps for multiple platforms (iOS, tvOS, etc.)
  • Want to collaborate with a co-worker on fixing an app crash? Use ReSwift Recorder to record the actions that lead up to the crash and send them the JSON file so that they can replay the actions and reproduce the issue right away.
  • Maybe recorded actions can be used to build UI and integration tests?

The ReSwift tooling is still in a very early stage, but aforementioned prospects excite me and hopefully others in the community as well!

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

Getting Started Guide

A Getting Started Guide that describes the core components of apps built with ReSwift lives here.

To get an understanding of the core principles we recommend reading the brilliant redux documentation.

Installation

CocoaPods

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

use_frameworks!

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

pod 'ReSwift'

And run pod install.

Carthage

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

github "ReSwift/ReSwift"

Accio

You can install ReSwift via Accio by adding the following line to your Package.swift:

.package(url: "https://github.com/ReSwift/ReSwift.git", .upToNextMajor(from: "6.0.0")),

Next, add ReSwift to your App targets dependencies like so:

.target(
    name: "App",
    dependencies: [
        "ReSwift",
    ]
),

Then run accio update.

Swift Package Manager

You can install ReSwift 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/ReSwift.git", from: "6.0.0"),
    ]
)

Checking out Source Code

After checking out the project run pod install to get the latest supported version of SwiftLint, which we use to ensure a consistent style in the codebase.

Demo

Using this library you can implement apps that have an explicit, reproducible state, allowing you, among many other things, to replay and rewind the app state, as shown below:

Extensions

This repository contains the core component for ReSwift, the following extensions are available:

  • ReSwift-Thunk: Provides a ReSwift middleware that lets you dispatch thunks (action creators) to encapsulate processes like API callbacks.
  • ReSwift-Router: Provides a ReSwift compatible Router that allows declarative routing in iOS applications
  • ReSwift-Recorder: Provides a Store implementation that records all Actions and allows for hot-reloading and time travel

Example Projects

  • CounterExample: A very simple counter app implemented with ReSwift.
  • CounterExample-Navigation-TimeTravel: This example builds on the simple CounterExample app, adding time travel with ReSwiftRecorder and routing with ReSwiftRouter.
  • GitHubBrowserExample: A real world example, involving authentication, network requests and navigation. Still WIP but should be the best resource for starting to adapt ReSwift in your own app.
  • ReduxMovieDB: A simple App that queries the tmdb.org API to display the latest movies. Allows searching and viewing details.
  • Meet: A real world application being built with ReSwift - currently still very early on. It is not up to date with the latest version of ReSwift, but is the best project for demonstrating time travel.
  • Redux-Twitter: A basic Twitter search implementation built with ReSwift and RxSwift, involing Twitter authentication, network requests and navigation.

Production Apps with Open Source Code

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

  • Thanks a lot to Dan Abramov for building Redux - all ideas in here and many implementation details were provided by his library.

Get in touch

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

We also have a public gitter chat!

reswift's People

Contributors

agentk avatar ben-g avatar cardoso avatar cruisediary avatar dagio avatar dcvz avatar delebedev avatar divinedominion avatar djtech42 avatar gistya avatar hoemoon avatar iron-ham avatar juggernate avatar langford avatar madhavajay avatar markszente avatar mickeyreiss avatar mikekavouras avatar mjarvis avatar mokagio avatar oradyvan avatar orta avatar qata avatar raheelahmad avatar richy486 avatar sendyhalim avatar thomaspaulmann avatar tkersey avatar vfn 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  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

reswift's Issues

Porting redux-loop to ReSwift with proof of concept

The comment by @Ben-G on #64 regarding redux-loop sparked my interest. There is an ongoing discussion over at Redux about Side Effects and Composition as a result of a tweetstorm by Dan Abramov, creator of Redux.

ReSwift has AsyncActionCreator which can dispatch multiple actions, but this puts all logic in action creators. I prefer the UI to dispatch simple actions that keep the UI interaction flow simple.

For example, if you have a fetchUserIfNeeded Action, which talks to an API, you would need to either:

  • Make the API global
  • Pass API into the action creator, meaning the UI needs to know about API
  • Have a class that stores reference to things like API, and also exposes Action Creators. In this case, the view could be passed "UserManager" and simply do store.dispatch(userManager.fetchUserIfNeeded()).

I prefer the second option for things like testing; however, I don't like the UI referencing API directly. The UI wants a user, it doesn't care how it happens.

Two promising Redux projects are redux-loop and redux-saga, which were also mentioned in #64. I've started to work on a port of redux-loop to ReSwift and wanted to get a discussion going about the best way to integrate this with ReSwift.

Implementation Issues

I created a proof of concept which can be found here.

Enhanced Store

For the proof of concept, I extended Store<StateType> and replaced both the dispatch and _defaultDispatch functions. The former to run any effects returned by the reducers, and the latter to check what the reducers returned for any effects, and if so store them so dispatch would be able to run them.

Reducers Return Value

Since reducers are required to return back StateType. The "enhanced loop store" needs the reducers to return back not only the new state, but also any effects it wants to run. redux-loop currently just returns back an Array.

In my proof of concept, I have a LoopStateType<StateType> protocol that stores the state and effect.

Conclusion

This proof of concept is very "hacky" and I would like to get a discussion going to see how we can make integrating a more "advanced" type middleware/store enhancer into ReSwift.

Substate Selection upon Subscription

As @Aleksion and I discussed, it would be great to be able to select a sub-state when subscribing to the store. This would allow us to programmatically ensure that the subscriber can only access a subset of the state.

Here's one idea of mine how we could potentially implement it, providing a closure that performs the sub select. We could typecheck that the closure returns the same type as is expected in newState.

override func viewWillAppear(animated: Bool) {
    ...
    store.subscribe(self, { $0.navigationState })
}

func newState(state: NavigationState) {
    contacts = state.dataState.contacts
}

@Aleksion also mentioned another approach using containers outside of View Controllers that would select and pass down a substate to a View Controller explicitly.

Next ReSwift Release

This has been overdue since a while; the Swift 2.2 fixes need to be shipped. There are some breaking changes in the API around standard actions.

Even though these most likely only affect the ReSwiftRecorder we should choose a new major release version number in case we decide to ship the change. It's a very minor breaking change to dictionaryRepresentation of StandardAction so I want to see if it might make more sense to revert this change & defer it to a future major release along with more breaking changes.

Will look into this next week!

Passing "sub Stores" for subscription

This comes up when passing down stores as dependencies.

Currently being able to subscribe for a sub-state is awesome, as the newState implementation is filtered down.
It would be nice to bring the same State filtering to Stores.

In other words, be able to generate sub-stores from the main store (just as a lens; there will still be a single concrete store).
Then we can pass the sub-store so that subselection is even more filtered.

As an example:

struct InstrumentState: StateType {
    let allInstruments: [Instrument]
    let currentIndex: Int?
}

protocol HasInstrumentState {
  var instrumentsState: InstrumentsState { get }
}

struct AppState: StateType, HasInstrumentState {
  let instrumentState: InstrumentState
}


class SmallSubscriber: StoreSubscriber {

  // --- Subscriber can then just expect a "smaller" Store
  init(store: Store<InstrumentState>) {
      store.subscribe(self) {
          return $0.current
      }
  }
}

// But if we start with appState
let instrumentsState =  InstrumentState(allInstruments: [...], currentIndex: 0)
let appState = AppState(instrumentState: instrumentsState)
let mainStore = Store<AppState>(reducer: MyReducer(), state: appState)

// then we can't pass that store to our subscriber:
let subscriber = SmallSubscriber(store: mainStore)
  • we then expose minimal information
  • better at scaling: large apps may have deep nesting of states in the store. Being able to pass a smaller store of a deeper nesting would be great

Maybe this is already possible?

Accessing state outside of action or reducer

Since we have a global store that is accessible anywhere, is it safe to use it as needed? Sometime outside of ReSwift we might want to check on the current state.

var myState = store.state.foo

or should all of this done inside actions / action creators?

Remove SwiftLint from Pod

I have my own ruleset for SwiftLint and your code can't pass it.
Please keep your code style trial on your local machine.

What about redux-saga for side-effects? [More discussion]

I read all the discussion about what should we do for side effects in this project. Redux-loop is gaining some attention, but I don't see anyone talking about the most popular extension for this kind of stuff: redux-saga. I asked about in the redux-loop thread (since most of the discussion is happening there) but got no response.

The reason I'm asking is that I'm willing to try and make a swift basic implementation for it. I have a simpler implementation of redux in swift I coded a few months back, so I could take a shot at it and integrating with this implementation (which is very close to my own) if it works well (or at all). But before that I wanted to check where are we at with these ports and if there is interest at all for another one.

Does this redux implementation support thunks?
Where are we at with the redux-loop one?
Should we even pursue a new approach?

Just wanted to get the discussion going (:

Potential issue: it is possible to subscribe to store updates multiple times

Since subscribers is an array (and Set is not really possible, there is no good way for subscribers to be Hashable) two consecutive subscribe calls will add subscriber two times, but unsubscribe removes only one instance.

Possible solutions:

  1. Fail fast with assert/abort
  2. Silently ignore next subscription (maybe log something to console)

Can we change Reducer from protocol into function type?

Most of the time we need to create a struct to wrap the underlying handleAction. If the same functionality can be done with typealias. Should we change to typealias so we don't need to wrap the real handleAction inside a struct.

How do you handle animations?

i am just getting started with it. so far i am impressed. but i am wandering how can i handle animations? what is a standard way to handle animations here?

Clean Up Action Types

The current naming around actions is messy and needs to be fixed.
Based on thoughts in #12 it seems like we will end up with three types of actions.

  • StandardAction the simple, serializable action with a type string and a serializable dictionary payload
  • StandardActionConvertible a typed action that can be converted from and to a StandardAction for serialization
  • An arbitrary type conforming to Action - these are the actions that cannot be serialized, they won't be able to work together with devtools (well)

If we follow this idea, Action will be an empty marker protocol with no requirements and devtools will treat StandardAction and StandardActionConvertible specially by providing support for serialization and deserialization.

Discussion: struct vs enum

The framework is awesome, I like it because it has many similar concepts of the Elm language.
But I want to know, would not it better to use a enum instead of structs, in the Actions ?

The new Reducer protocol forcing a reducer to hydrate the state

With the new Reducer protocol beings

public protocol Reducer : AnyReducer {
    typealias ReducerStateType
    public func handleAction(action: Action, state: Self.ReducerStateType?) -> Self.ReducerStateType
}

It force every reducer observing a particular ReducerStateType to hydrate the state.
If I have a reducer that do not want to hydrate the state or only want to handle the Action if the targeted State exits, I can't achieve it due to this protocol.
I think we should have a better way to handle this.

Animating table/collection updates

Trying to implement the proper animating in of sections (as Photos framework queries process in the background and I update a UICollectionView), I am running into some problems.

I track my pending changes which are part of the app state. In newState I look for those and if found I process them in a performBatchChanges block on the collection view. At the end I dispatch a PhotoActions.PendingChangesHandled(changes) action to notify the store. I'll call this the ACK message for brevity.

At that point the app dies because I'm dispatching while in the middle of a dispatch.

Here are the potential solutions as I see them:

  1. Dispatch to a background queue, then from there dispatch back to the main queue, then dispatch the ACK. This introduces logic errors because the Photos framework query is still being processed and sending even further updates asynchronously but the pending changes array in the app state hasn't had the resolved changes removed. So if one of those further state changes slips in-between, the collection view will attempt to re-apply pending changes it has already applied and Bad Thingsβ„’ will happen.
  2. The store can perform a completion block but the problem is this is really communication between the VC and Store of something it did after the fact so it would require the action creator, VC, and store/reducers to collude in a really tangled way. This seems like it is just a path to re-introduce complexity.
  3. Have the VC flip a property on the PendingChange object to mark it as handled. This is gross because it directly contradicts the spirit behind the framework by having the view controller mutate app state directly... besides the fact that doing this in other scenarios could introduce race conditions and various other concerns.
  4. Have the VC remember PendingChange objects it has seen and ignore them on further state updates, and combine this with (1) to eventually clean old ones out of the list. This is just introducing mutable state back into the VC
  5. Add a facility to inject a post-dispatch completion function which the VC could use to make sure its ACK was sent after dispatch was finished but before any new actions were processed. I'm leaning towards this only because it is easy to implement and I need to get this project out the door :)
  6. If you really wanted to get fancy the VC could compare the old state to the new state and figure out what sections or objects were added, removed, moved, etc, then animated those changes to match. That's a fairly ambitious undertaking but would align closer to what React is doing with Virtual DOM updates.

Maybe I'm over-thinking it? If you're using React then you avoid the problem by Virtual DOM diffing which makes computing the delta between (old state -> rendered view) and (new state -> rendered view) really cheap (though I haven't seen anyone take it a step further and apply animation rules based on the Virtual DOM diffs).

I thought about having a mechanism specifically to allow the view layer to acknowledge actions for cases like this. I don't know if this is a problem that will come up in other situations or if it is very specific to handling animations (which are stateful by nature).

(6) just begs for a virtual UIKit system akin to React's Virtual DOM to generalize it but that would be a massive undertaking.

Store.ActionCreator , Store.AsyncActionCreator and Middlewares

As far as I understand, all overloads of Store.dispatch that using both Store.ActionCreator and Store.AsyncActionCreator types wrap _defaultDispatch actually. It seems like a job for middlewares, isn't it?

BTW, why does Middleware take references to dispatch and getState separately? Maybe it is worth to extract both from StoreType to some StoreCoreAPIType and pass it by weak reference.

P.S. ReSwift is very interesting framework πŸ‘

Create a convention for how to handle async actions and side effects

There are a lot discussions going on in the ReduxJs community regarding side effects and how they should be handled. And I think we should incorporate what we think of as best actions into the core project (we don't have an example of how it can be achieved in our docs currently).

From what I've gathered there are currently 3 options:

1: Action Creators
2: Reducers
3: Middleware

1: Dan Abramovs initial suggestion is to implement side effects in Action Creators. However, a lot of people find this to become messy quite quickly.
Personally, I also find it hard to figure out how to structure my application properly. If most actions are simple, and some are huge complex chunks of code, I find it hard to jump into an application and figure out where to find API logic fx.

2: The following discussion highlights why using Reducers for side-effects is a bad idea: reduxjs/redux#291. And personally I agree. Reducers should change state objects, but not dispatch side effects. If we start putting side effects in some reducers and not in others, it will quickly become hard to track how information flow through the application.

3: Middlewares: There are quite a few examples of middlewares handling sideeffects in Redux - one of them is https://github.com/yelouafi/redux-saga. Personally I lean towards this approach. I dislike the name - because it has no semantic meaning ot me. But the idea of having services, that catch actions before they're passed on to reducers, is an appealing one.
Personally I would structure them as services in the application, so that all the side effect logic is located in one place. Disclaimer: I haven't read the Saga documentation in detail, so I might have a different mental picture what it's actually doing.

I'm going to keep updating this thread as I read through the documentation of all the different solutions. But feel free to pitch in in the meantime!

Provide Store Enhancing Mechanism

We should provide a store enhancing mechanism, just as Redux, that allows us to extend the stores capabilities without subclassing. Eventually the Swift-Flow-Recorder should become a store enhancer instead of a store subclass.

Automatic serialisation & deserialisation of actions and state

You can do a surprising amount with introspection in Swift, these functions can encode a struct or class to and from a [String: Any]:
https://github.com/davidlawson/SwiftKVC/blob/master/SwiftKVCTests/SwiftKVCTests.swift#L60-L98

The only disadvantage is that you would need to keep an array of Action.Type around for the deserialisation process. Perhaps nicer than code generation though?

A Swift JSON library (e.g. SwiftyJSON – using NSJSONSerialization uses non-Swift objects) or equivalent could then be used to persist the data.

(This may also work well for serialising state as well as actions.)

Another similar library: https://github.com/sagesse-cn/swift-serialize

Limitations with this approach:
– deserialisation requires there to be an init method with no parameters (created by default if initial values are provided)
– serialisation does not support implicitly unwrapped optionals
– deserialiser requires array of action types to work
– seems to be fighting the language a bit

State change with new array items

If your state contains an array property, and you append a new item to the array, is there a way to determine which item was added to the state? Is there a way to get a diff between previous state and the new state?

[Question] General question about this architecture

I have a general question regarding architecture (not necessarily the implementation provided), particularly the order of which state change occurs, and which state is passed to the subscribers.

Hopefully somebody can clarify this for me .

Assuming I have multiple reducers for a given action, in what order should the state changes occur? Also would a subsequent reducer be passed in the state from the previous reducer? If so, how would you assure that they are performed in the correct order?

For instance an action which is represented like:

Action : {
"action-type" : "Action-type-xxx",
...relevant information ....
}

and 2 reducers:

Reducer1:
reduce(Action-type-xxx action_to_apply, State currentState) -> newState

Reducer2:
reduce(Action-type-xxx action_to_apply, State currentState)

what should happen in the Store when dispatching an action? are they applied in order , given the state from the previous action?

Store:
reduce(action) {
// ?
for reducers in [reducer1,reducer2] {
newState = reducer(action,currentState) // ? which state would be passed into the second reducer
// is currentState = newState applied at this point?
}
// which state would be passed to the subscribers? the last state produced I am assuming
}

On the same token, what would happen if I have a list of subscribers, and one of subscribers would trigger an action on "state change"? Would the subsequent subscribers get the same state as the first subscriber , or would they get the updated state that the previous subscriber created?

State Serialization

Currently only actions can be serialized, but not the application state. This means time traveling and state restoration are only possible by replaying a sequence of actions, not by setting the application to an arbitrary state.

We should evaluate how much benefits a serializable state would bring (for me crash reporting comes to mind) and figure out if there's a convenient way for developers to implement state serialization or if we can generate the required code.

Improving ReSwift Documentation / Examples

Had this at the back of my mind for quite a while. We should discuss the roadmap for getting ReSwift to 1.0, since we've been really close for a while.

On my list I have:

  • Solid Example App (working on one right now; the more the merrier)
  • Updated Docs (a good portion of documentation is slightly out of date with recent API changes)
  • Nice to have: best practice / new API for async operations (can be post 1.0 as well)

I'll be traveling a fair bit in March, so my goal is to ship 1.0 in April.

My plan was to push ReSwift (core) to 1.0 and potentially the router as well and keep ReSwift-Recorder in an experimental state for now.

Thoughts, ideas, concerns?

Let's use this thread to discuss other things that might be important.

ActionCreator returning multiple actions

Great job on ReSwift so far. Ever since Flux was released I was interested in a project such as this.

There are cases where an ActionCreator would be very useful. For example, looking at GitHubBrowserExample, the action authenticateUser dispatches two actions and then returns nil.

Redux has middleware (e.g. redux-multi) that allow returning multiple actions. Is this something the ReSwift community would be interested in? If so, should ReSwift support it directly or have a middleware project that does?

Side effects in newState?

What is the best practice on performing side effects, like presenting a UIAlertController in newState? Is this alright?

    func newState(state: LoggedInState) {
        self.stopLoading()
        switch state {
        case .ErrorLoggingIn(let error):
            presentViewController(UIAlertController(okayableTitle: "Couldn't log in πŸ˜”", message: error.localizedDescription), animated: true, completion: .None)
        case .LoggedIn:
            print("Logged in")
        case .NotLoggedIn:
            print("still not logged in")
        case .Logout:
            assertionFailure("Cant logout from this screen")
        }
    }

Make Action Serialization Opt-In

Currently the library requires all actions to be serializable. After brief discussion with @russbishop I would like to follow his suggestion and make them opt-in.

This will have the downside that some developers will not immediately understand that developer tools (time traveling) won't work unless all actions are serializable, but we can work with warnings, log messages, etc. to communicate that.

Many existing Obj-C/Swift frameworks provide objects that are hard to serialize and therefor could not easily be used within actions.

Store subscription promotes retain cycles in view controllers

The natural way to approach store subscription is to subscribe in viewDidLoad, then unsubscribe in deinit. Because the Store holds a strong reference to the VC this promotes a retain cycle. I feel like this is a bear trap that people will repeatedly step in so maybe it is worth discussing potential solutions.

  1. Tell people not to hold a reference to the store, just access it as a global. This seems like a bad idea and bad design.
  2. Tell people to hold a weak reference to the store. This relies on everyone doing the "right thing" which we already know they don't do.
  3. Have the store hold subscribers as a weak reference (I have a WeakBox that could be used for this). This means putting unsubscribe in the deinit should work just fine to keep the subscriber array clean. When dispatching if we encounter a nil subscriber we could remove it or assert depending on whether we consider this a programmer error or not.
  4. Tell people to subscribe in viewWillAppear and unsubscribe in viewWillDisappear.

It might be obvious but I lean heavily toward 3 because it eliminates a headache for users of the library and allows them to rely on the existing UIKit/AppKit mechanisms for managing View Controller lifetimes. My temporary workaround is to unsubscribe from viewWillDisappear but that is problematic for VCs that will in fact re-appear and 4 means you can't react to state changes if you aren't on-screen.

I'm happy to submit a PR for this one if there is consensus on fixing it.

It is possible for ReSwift to have Selector?

In Redux, there is a β€œselector” library . It makes computing derived property from state more performance and thus encouraging minimal possible state. I think this fit very well into the current sub-state subscription.

I am not sure weather we can implement similar thing in Swift.But If its possible, I think its a good function to have in ReSwift.

Make Dispatching Synchronous

Currently dispatching is implemented using dispatch_async - that guarantees that all subscribers to the store will receive the latest state, before another state change can occur.

I however haven't encountered a lot of cases where this guarantee is necessary; usually code should not rely on receiving on intermediate state updates anyway.

The async dispatch also has a bunch of downsides - it makes the store harder to test and it makes it harder to debug the flow of actions in through the backtrace.

In short, dispatching should become synchronous.

[question] where to draw the line with state

I love this concept but I have some questions around where you envision the line being drawn with actions and state.

For instance, I know that in a UITabBarController example you override the default behavior and handle the event manually. How does something like this translate to a UISwitch? Is it overkill for this example and better to just receive the change and then post and action that the state of a switch changed?

Additionally there are lots of things in the UI realm, for instance scroll events on a tableview. In order to FULLY capture the state change these would be tracked but unless it is relevant to a given application this also seems like overkill.

Thoughts?

Thanks and great work!

Should we crash when cast to subscriber-specific state fails?

Subscribers can request a specific sate type, by changing the method signature of the newState method, e.g.:

    func newState(state: HasAuthenicationState) {
        loggedInLabel.text = "\(state.authenticationState.userAuthenticated)"
    }

This is very useful as it allows us to restrict access to only the relevant sub-state and it empowers extensions such as SwiftRouter that can see the app state through the lense of a particular protocol that they define themselves.

If the state cannot be casted this currently fails silently, we should consider crashing with fatalError since this should never occur unexpectedly after shipping an app, but would be useful during development.

Store entities do not deinitialize (dealloc) when no longer owned (Memory Leak)

How to reproduce:

  1. Add a deinit callback to the Store class to know that a store has been deallocated
  2. New up a store in memory that is not owned (meaning it should be deallocated on the next run loop)
  3. deinit will not get called at runtime, meaning the store has a memory leak
  4. Comment out the following lines:
    self.dispatchFunction = middleware.reverse().reduce(_defaultDispatch) {
    [weak self] dispatchFunction, middleware in
    let getState = { self?.state }
    return middleware(self?.dispatch, getState)(dispatchFunction)
    }
  5. Re-test, store now deallocates properly

It appears that the dispatch function is ultimately holding a reference to the store object itself, making it impossible for the store to go away and free up the memory it was using if it was using. Consequently it also holds onto any reducers and subscribers. Allowing the store to deallocate will allow all referenced objects to deallocate as well.

[question] store mechanism

just wondering after watching your presentation in realm, how your mechanism to store your all state? are u using some file storage or core data?

Thanks

Make last action available to subscribers

While implementing Redux I started seeing the propagation of messages being sent to subscribers as the number of actions and state properties increase.

For a small app, where just a few properties represent the state of the app, and the complexity of re-rendering the new state is not a big deal, it doesn't hurt to just update the UI or do a quick comparison to see if an update is needed.

For cases where updating the UI or comparing the previous with the current state is a complex task, it would be useful to allow subscriber to know what action has been triggered and leave to the subscriber to decide whether it should re-render or not.

The issue reduxjs/redux#580 has been closed by the redux team as it doesn't fit with the React paradigm. Unfortunately most of us cannot rely on a framework like React to do the diff and answer the question What should be rendered again?.

With this in mind, I'd like to propose or a change to the AnyStoreSubscriber/StoreSubscriber to have the state added to the functions...

public protocol AnyStoreSubscriber: class {
    func _newState(state: Any, actionType: Action.Type)
}

public protocol StoreSubscriber: AnyStoreSubscriber {
    typealias StoreSubscriberStateType

    func newState(state: StoreSubscriberStateType, actionType: Action.Type)
}

....


// MARK: - StoreSubscriber methods on a random subscriber

extension MyRandomViewController: StoreSubscriber {

    func newState(state: AppState, actionType: Action.Type) {
        guard actionType == MyRandomAction.self else { return }

        // Handle new state
    }

}

...or the addition to the documentation on how to appropriately handle this necessity. One way would be to add a lastAction property to the state.

Any thoughts?

Stronger types for actions

Hi, I wanted to start conversation about introducing stronger types into the framework.
I implemented a test app using the latest version of the framework to get a feel of it. Also I started playing with the barebones version implementation of the framework in the playground (Flow.playground.zip).
My core objective for now is to make action type stronger. Also I experimented with implementing it as an enum, and, while can't be enforced on the level of type system, it exposes several advantages comparing to the string type + dictionary payload approach:

  1. Autocompletion, makes typos impossible
  2. Looking at action object, it is quite easy to guess what are the possible actions
  3. It's also quite easy to know if you handled all possible action*
  4. Most important one, associated values, allows to strongly type payload.

Also it has several disadvantages:

  1. Enum with many cases. In any sufficiently large app it might become a huge file (since currently it's not possible to extend existent enum)
  2. Same problem from a different perspective: reducers are unlikely to handle all the possible actions in one function (which would be impractical). It means in practice we will end up using default: case or if case let … = state to handle a particular case.

Here is a gist of my simplified implementation:

  • Action is a generic type, experiment with implementing it using enum with associated values for payload
  • Concrete store is implemented as a generic class over state and action types
  • Use typealias functions where possible (reducers) instead of protocols
  • I was trying to avoid import Foundation but there is still need for libdispatch (however it seems to be ported to linux)

My implementation misses many important features that you have in yours.
But I wanted to get the conversation going with this little sample:)

[Question] Implementing a database

I am a little unclear on how one might implement a database with this architecture.The initial app state could be created from a database and passed in at startup. However, writing to this database seems a little more tricky.

One option would be to just subscribe a db layer to the store changes and it would be responsible for persisting whatever portions of the state it thought was relevant. The catch here is that you could introduce a lot of unnecessary DB work as the new state doesn't contain the action that caused the state change, so you'd have to just re-save everything (unless the db was responsible for calculating a diff between its current state and the new state).

Alternately, the reducers could be in charge of saving incremental changes. This seems to have some benefits since the logic for a specific action is there already, although this feels wrong based on the design decisions of the architecture.

Any suggestions would be greatly appreciated.

Generate Code for Action Serialization

Currently a lot of boilerplate code is required in order to turn typed actions into serializable actions of type Action. Developers need to implement the ActionConvertible and ActionType protocols manually, e.g.:

struct CreateContactFromEmail {
    static let type = "CreateContactFromEmail"
    let email: String

    init(_ email: String) {
        self.email = email
    }
}

extension CreateContactFromEmail: ActionConvertible, ActionType {

    init(_ action: Action) {
        self.email = action.payload!["email"] as! String
    }

    func toAction() -> Action {
        return Action(type: CreateContactFromEmail.type, payload: ["email": email])
    }
}

For most actions this could can and should be generated automatically. Using SourceKitten this should be fairly straightforward.

Recursive dispatch

Currently subscribers are called just before default dispatcher returns its result. If one of subscribers produce an action then we get recursive dispatch.

Example:

struct NewStringValue: Action {
    let value: String
}

class StringReducer: Reducer {
    func handleAction(action: Action, state: String?) -> String {
        let newString = action as! NewStringValue
        return newString.value
    }
}

class StringSubscriber: StoreSubscriber {
    var count = 0
    func newState(state: String) {
        if count < 3 {
            count += 1
            stringStore.dispatch(NewStringValue(value: state + "@"))
        }
    }
}

// I modified Middleware signature slightly while I have been experimenting on ReSwift.
let loggingMiddleware: Middleware = { store in
    return { next in
        return { action in
            print("action: \(action)")
            let result = next(action)
            print(store?.optState ?? "state is not hydrated yet")
            return result
        }
    }
}

extension String: StateType { }

let stringStore = Store(reducer: StringReducer(), state: "", middleware: [loggingMiddleware])
let subscriber = StringSubscriber()
stringStore.subscribe(subscriber)

Output:

action: NewStringValue(value: "@")
action: NewStringValue(value: "@@")
action: NewStringValue(value: "@@@")
@@@
@@@
@@@

Is it by design? Workaround is to dispatch actions asynchronously.

Can not use CombinedReducer in the same way as in ReduxJS

My app has an AppState with 4 substates: SearchScopeState, CloudState, AccountState, FileState. I also created one AppReducer with 4 other reducers SearchScopeReducer, CloudReducer, AccountReducer, FileReducer.

Here is my AppReducer

struct AppReducer: Reducer {
    func handleAction(action: Action, state: AppState?) -> AppState {
        var state = state ?? AppState()
        state.searchScopeState = SearchScopeReducer().handleAction(action, state: state.searchScopeState)
        state.cloudState = CloudReducer().handleAction(action, state: state.cloudState)
        state.accountState = AccountReducer().handleAction(action, state: state.accountState)
        state.fileState = FileReducer().handleAction(action, state: state.fileState)
        return state
    }
}

Here is my store

let mainStore = Store(reducer: AppReducer(), state: AppState(), middleware: [loggingMiddleware])

Now I want to use a CombinedReducer of 4 child reducers. This way I can remove AppReducer, let CombinedReducer do its job. Here is new setup

let combinedReducer = CombinedReducer([SearchScopeReducer(), CloudReducer(), AccountReducer(), FileReducer()])
let mainStore = Store(reducer: combinedReducer, state: AppState(), middleware: [loggingMiddleware])

But when I run the app, the handleAction method of each reducer in the new setup would not have been called. While from the Redux documentation, I can understand that CombinedReducer can help to consolidate substates into one state.

P/S: I have just stated getting familiar with Redux and ReSwift

Confusion about AsyncActionCreator and ActionCreator's accesibility

First: huge thanks for this, it's super interesting!

I came across the problem that in the current released version (the one that cocoapods picks up), AsyncActionCreator and ActionCreator are not public typealiases, which was super confusing at first. Would it worth to release a new version soon? I saw this has been fixed since then.

Cheers!

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.