Unidirectional Data Flow in Swift - Inspired by Redux

Overview

ReSwift

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

Supported Swift Versions: Swift 4.2, 5.x

For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or earlier.

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: "5.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: "5.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!

Comments
  • ReSwift crashes on device (but not simulator)

    ReSwift crashes on device (but not simulator)

    Help! I have been chugging along using ReSwift (on 3.0, Swift3, XCode 8.3) and now for some reason upon initialization ReSwift is crashing (when I run it on my 10.3.1/iPhone7+ thru XCode). I hate these esoteric errors and am hoping beyond hope that someone has experienced anything remotely similar.

    AppDelegate has the usual declaration

    let store = Store<State>(reducer: AppReducer(), state: nil)
    

    Then my first ViewController subscribes:

    override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            
            store.subscribe(self) { state in
                state.authenticationState
            }
     }
    

    ReSwift goes through the initialization process until it gets to my AppReducer, and BAM !

    screen shot 2017-04-21 at 10 30 24 pm

    Thanks everyone,

    --Bill

    Needs Update 
    opened by snydersaurus 45
  • ReSwift 5.0 Release Plan

    ReSwift 5.0 Release Plan

    Here's what I found in the issues and current PRs. Please add and discuss.

    I also created a GitHub project from this list, in case that makes contributing to the roadmap any easier: https://github.com/ReSwift/ReSwift/projects/1


    Low-Hanging Fruit:

    • [x] Fix memory leaks, see #302
    • [x] Finish up PR #300 as a fix #298
    • [x] Approve #295
    • [x] ~Review #231 (generic ActionCreator); it is missing an AsyncActionCreator counterpart~ ActionCreator will be deprecated in favor of ReSwift-Thunk in the future

    Breaking Changes

    • [x] Perform making state setter private as per #264 (breaking API change, bump to 5.0?)
      • [x] Deprecate ReSwiftRecorder, make outdated dependency on v3.x transparent in the README, look for new maintainers in the community?

    Consider including

    • [ ] ~Avoid double equatability checks, as per failing #259, needing a fix~ (deferred to post 5.0)

    Release Preparation

    (Done manually, because #248 never got implemented :))

    • [x] Update docs
    • [x] Collect contributions for the release notes
    • [x] Update README
    Help Wanted 
    opened by DivineDominion 37
  • Skip Repeats Implementation

    Skip Repeats Implementation

    Thanks a lot to @mjarvis and his work in #190. As part of reviewing his change I noticed some of the aspects that I think could be improved. It would be nice to add a feature that allows skipping state updates that haven't changed a certain substate to the core of ReSwift, as a lot of folks have been asking about that.

    However, after some prototyping, I think that the implementation of this should live in the subscription and not in the store. Looking at this prototype, the changes become a lot less invasive that way.

    Essentially our subscription type is very, very lightweight reactive component that transforms new values coming out of the store. By keeping the logic in the subscription, we make that reactive component extensible, independent of the rest of ReSwift. We can add a few operators to it (such as skipRepeats) that are going to be used by a majority of developers (however, we should refrain from implementing a full signal library, since enough great libraries exist in that space).

    The implementation right now is still pretty hard-wired. I would like to improve this, such that all the transformation logic of new values lives in the subscription and such that various transformations can be composed (as in reactive libraries).

    But since I'm almost out of time for this weekend, I wanted to share this working prototype for now.


    To-do list

    (by @DivineDominion: Just to end up in the PR overview, I pulled the comments here as a todo)

    • [ ] Provide alternative API for creating a subscription that doesn't require using a closure
    • [x] Make equatable substates use skipRepeats by default
    • [ ] Potentially find a new name for Subscription/skipRepeats, though I haven't heard suggestions on this yet.
    • [ ] document subscription API change:
        // If the same subscriber is already registered with the store, replace the existing
        // subscription with the new one.
        if let index = subscriptions.index(where: { $0.subscriber === subscriber }) {
    
    opened by Ben-G 32
  • Dispatch issue with middleware.

    Dispatch issue with middleware.

    I have discovered a likely crash scenario. Please see below.

    Store's dispatchFunction var gets assigned in ReSwift/CoreTypes/Store.swift ll. 69–79:

        self.dispatchFunction = middleware
            .reversed()
            .reduce({ [unowned self] action in
                return self._defaultDispatch(action: action)
            }) { dispatchFunction, middleware in
                // If the store get's deinitialized before the middleware is complete; drop
                // the action without dispatching.
                let dispatch: (Action) -> Void = { [weak self] in self?.dispatch($0) }
                let getState = { [weak self] in self?.state }
                return middleware(dispatch, getState)(dispatchFunction)
        }
    

    However on line 76 we have:

    let dispatch: DispatchFunction = { [weak self] in self?.dispatch($0) }

    Now, self?.dispatch($0) in turn calls this function on ll. 146-148:

     open func dispatch(_ action: Action) {
         dispatchFunction(action)
     }
    

    In turn, the Store's dispatchFunction function var gets called here. However, at this point, dispatchFunction may be nil because ll. 69–79 may not have completed its task of assigning a value to it yet, and it has no default value. See it's declaration on line 35:

    public var dispatchFunction: DispatchFunction!

    This may crash when the middleware array is not empty coming into the init function and one of them calls dispatch, since if the .reduce function runs, it may call Store's instance var dispatchFunction before it has been initialized.

    EDIT: This will not always happen, but it can happen depending on how middleware is structured. There is also an infinite loop crash that's possible; see below.

    Needs Discussion 
    opened by gistya 27
  • Use ReSwift in modular app

    Use ReSwift in modular app

    Hi, I have some question about using ReSwift and REDUX pattern in modular app.

    Modular app is one applications which divided into frameworks. An example to framework can be:

    • Core Framework - contains core capabilities which are shared in the app (like: security, user defaults and more)

    • Network Framework - contains all the network requests

    • UI Frameworks - to share and reuse UI components inside the app like: Buttons, TextFields, Table views and more.

    The major part in each modular app is the Feature Frameworks which are a code for specific feature inside the big app. Such features can be: HomeFeature, ProfileFeature, SettingsFeature, FeedFeature and more and more according to the app capabilities. Each feature should not be depend on other feature.

    The question is how ReSwift can be used in such architecture? The challenge is how to share the state (which should be global) between the Feature framework. Let's say that we create the AppState on the App level (in the app delegate) and the app is depended on all the Feature framework. Inside each feature framework we will create the specific redux components for this specific feature (e.g. in HomeFeature we will have HomeState, HomeReducer and HomeActions which are relevant only to this feature).

    Now, in such architecture the App knows the HomeState and HomeReducer because they are exposed by the Feature framework and we have dependency but the problem is that the Feature framework is not depended on the App and the feature framework don't know what is an AppState or what is a Store so we cannot dispatch actions in it.

    I hope that my what I describe above is clear... and would like to hear your thoughts about it. I think ReSwift is a great and very powerful library and if we will be able to create a modular app and still utilize this pattern it will be awesome!

    Thanks.

    opened by ranhsd 23
  • Integration of FRP Frameworks

    Integration of FRP Frameworks

    Unidirectional Data Flow and FRP play together nicely. We should consider how to provide an integration of FRP frameworks. Currently it's fairly easy to role your own, on top of Swift Flow, as shown in this example: https://gist.github.com/Ben-G/c33486169d68a6f3d12f

    We should consider if integrations should become a core part of the framework or not; related discussion in ReduxKit.

    Needs Discussion 
    opened by Ben-G 22
  • Facing EXC_BAD_ACCESS when projects and states become large and nested

    Facing EXC_BAD_ACCESS when projects and states become large and nested

    Hi, Thanks for the amazing framework. Currently our project are using it heavily. But as the project grows, we are facing problems. The most common one is the 'EXC_BAD_ACCESS' issue.

    00 11 22 33

    This happens when I started to add a new state. The app will crash if the state contains one or more enum properties with associated value. If I simply changed the state to a simpler struct, the app won't crash most of the times. Sometimes, crash also happened when I tried to add a substate inside another substate(Eg: add a state in EntitiesState. I have to move that substate out of the EntitiesState to avoid the crash).

    This only happens when we have about hundreds of different states and substates.

    I would be very appreciated if you could give me some help. Thanks a lot!

    opened by fans3210 20
  • Subscription.select should not surprisingly skipRepeats

    Subscription.select should not surprisingly skipRepeats

    This basically removes the option to select a substate which is Equatable without skipping repetitions.

    Why would anybody not want to skip repetitions?

    I certainly do, but I don't want to do this using the == operator, especially not for the huge collections of notes I manage in this one app. Right now I cannot pass a substate along directly. Instead, I have to create a subscription wrapper that selects the substate and passes it on to the real subscriber.

    I think this side-effect of select hurts, and writing .select(...).skipRepeats() shows at the call site what'll be going on. No surprises.

    opened by DivineDominion 20
  • [Question] Filtered Subscriptions?

    [Question] Filtered Subscriptions?

    if I have substate1, substate2 and uiviews filtered subscribers view1, view2 in one ViewController. Now when I change substate2, the newState() is being fired for the view2 and for the view1, although substate1 isn't changed. To fix this I confirms the substates to Equatable protocol and when the newState() is fired check if the new substate is equal to the old one and if it's not then change the view properties.

    Is there more practical way to do that? ReduxTest.zip

    Question 
    opened by sisomollov 20
  • State normalization

    State normalization

    Hi! Coming from redux I'm really having lots of fun with ReSwift, but I just can't decide what's the best choice for handling reational data in case of swift.

    In js world, in redux we mess with normalizr and selectors in order to benefit from normalized state and keep relational data consistent, I assume same pattern applies for ReSwift?

    With structs being value types it would require some manual work to keep nested structures consistent, classes on the other hand, don't have all advantages of immutability, but being reference types, they don't need to be synchronized...

    So what would be the best approach here?

    opened by khrykin 19
  • Implemented `redux-thunk`-like action creator

    Implemented `redux-thunk`-like action creator

    This implementation allows us to write action creators with return value, just as redux-thunk middleware do. For example it allows composition of action creators this way:

    protocol Sauce {
    }
    
    protocol SandwichServiceType {
        func getSecretSauce() -> Promise<Sauce>
    }
    
    struct MakeSandwich: Action {
        let person: String
        let sauce: Sauce
    }
    
    struct Apologize: Action {
        let fromPerson: String
        let toPerson: String
        let error: Error
    }
    
    class SandwichActor {
        let service: SandwichServiceType
        init(service: SandwichServiceType) {
            self.service = service
        }
        
        func makeASandwichWithSecretSauce(for person: String) -> ActionCreator<Promise<Void>> {
            return { (store) in
                return self.service.getSecretSauce().then { (sauce) in
                    store.dispatch(MakeSandwich(person: person, sauce: sauce))
                }.recover { (error) -> Void in
                    store.dispatch(Apologize(fromPerson: "Sandwich store", toPerson: person, error: error))
                    throw error
                }
            }
        }
        
        func makeSandwichesForEveryone() -> ActionCreator<Promise<Void>> {
            return { (store) in
                return firstly {
                    store.dispatch(self.makeASandwichWithSecretSauce(for: "Grandma"))
                }.then {
                    when(fulfilled:
                        store.dispatch(self.makeASandwichWithSecretSauce(for: "Me")),
                        store.dispatch(self.makeASandwichWithSecretSauce(for: "My wife"))
                    )
                }.then {
                    store.dispatch(self.makeASandwichWithSecretSauce(for: "Kids"))
                }
            }
        }
    }
    
    // And finaly use this:
    
    let sandwichActor = SandwichActor(service: realService)
    
    store.dispatch(sandwichActor.makeSandwichesForEveryone())
    

    What do you think about this?

    opened by mpsnp 18
  • Filter on updates not working

    Filter on updates not working

    Hi All, given the following class that inherits from StoreSubscriber, I'm trying to subscribe only to a sub-state. But even if another sub-state is updated, then this subscriber is notified about an update.

    class PortWindowService: StoreSubscriber {
        typealias StoreSubscriberStateType = ModelState
        
        func start() {
            store.subscribe(self) { $0.select { $0.modelState } }
        }
    
        deinit {
            store.unsubscribe(self)
        }
        
        func newState(state: ModelState) {
            print(state.ports.count)
            print("ports changed")
        }
    }
    

    My app state is:

    struct AppState {
        var modelState: ModelState = ModelState()
        var volumeState: VolumeState = VolumeState()
    }
    
    struct ModelState {
        var model: StoredModel? = nil
        var ports: [Port] = []
    }
    
    struct VolumeState {
        var volumes: [VolumeInfo] = []
    }
    

    And my reducers are:

    func appReducer(action: Action, state: AppState?) -> AppState {
        return AppState(
            modelState: modelReducer(action: action, state: state?.modelState),
            volumeState: volumeReducer(action: action, state: state?.volumeState))
    }
            
    func modelReducer(action: Action, state: ModelState?) -> ModelState {
        var state = state ?? ModelState()
        
        switch action {
        case let action as SetModel:
            state.model = action.model
            state.model?.usbPortsLeft.enumerated().forEach { (index, portNumber) in
                state.ports.append(Port(portNumber: portNumber, state: .unknown, area: .left))
            }
            state.model?.usbPortsRight.enumerated().forEach { (index, portNumber) in
                state.ports.append(Port(portNumber: portNumber, state: .unknown, area: .right))
            }
            state.model?.usbPortsBack.enumerated().forEach { (index, portNumber) in
                state.ports.append(Port(portNumber: portNumber, state: .unknown, area: .back))
            }
        default: break
        }
        
        return state
    }
    
    func volumeReducer(action: Action, state: VolumeState?) -> VolumeState {
        var state = state ?? VolumeState()
        
        switch action {
        case let action as AddVolume:
            if let volumeInfo = action.volume {
                state.volumes.append(volumeInfo)
            }
        case let action as RemoveVolume:
            if let basePath = action.basePath {
                state.volumes.removeAll(where: { $0.volumePath == basePath })
            }
        default: break
        }
        
        return state
    }
    

    The SetModel action is only called twice during startup. So I assume that "ports changed" is only printed twice (see func newState in the PortWindowService class). But even when the actions AddVolume or RemoveVolume are called in a different reducer, in a different sub-state, which is not selected during subscription in the StoreSubscriber initialization, I'll get an event and newState is called.

    What am I missing here? How can I make sure that with store.subscribe(self) { $0.select { $0.modelState } } newState is only called when anything in modelState changes, but not in volumeState?

    opened by sarensw 3
  • Release 6.1.1

    Release 6.1.1

    • [X] bumped Xcode marketing version
    • [X] bumped CocoaPod version
    • [ ] Add release date to the changelog when merging

    Edit: Motivation: to release a Swift 5.7[.1] binary with recent changes.

    opened by DivineDominion 3
  • How to use a state on recursive screens?

    How to use a state on recursive screens?

    For example, when you have UserState in ViewControler that displays a user and posts, what is the best practice to do?

    • UserViewController -> UserViewController -> UserViewController...
    • UserViewController -> PostViewController -> UserViewController -> PostViewController...
    struct UserState: Equatable {
        var user: User
        var posts: [Post] = []
    }
    

    Can I implement something like below?

    struct AppState: Equatable {
        // var user = UserState()
        var users: [UserState] = []
    }
    

    I am new to ReSwift and would appreciate any advice you could give me :bow:

    opened by toomozoo 4
  • Crash when dispatching actions from inside StoreSubscriber.newState()

    Crash when dispatching actions from inside StoreSubscriber.newState()

    Screenshot 2022-12-07 at 18 16 47

    We have several actions that are chained one after another in this manner:

    1. Action is dispatched
    2. A StoreSubscriber receives newState callback on state change
    3. It dispatches another Action on the same Store
    4. Another StoreSubscriber (or maybe the same one) receives newState callback on state change
    5. It dispatches another Action on the same Store

    These chains are not infinitely recursive, their depth is 2-4 actions. They all operate on single (main) thread synchronously. We see a crash when 2nd or 3rd dispatch is called inside ReSwift. It occurs with different action sequences. One case we can reproduce 100% in debug configuration (-Onone), but it's not reproduced in release configuration.

    Stacktrace top is __swift_instantiateConcreteTypeFromMangledName

    opened by gmorning 2
  • Huge CPU gap between ReSwift Store vs just SwiftUI ObservableObject

    Huge CPU gap between ReSwift Store vs just SwiftUI ObservableObject

    First of all, huge thanks for creating and maintaining ReSwift; it’s a pleasure to use!

    I noticed a big CPU gap between ReSwift Store vs just a SwiftUI ObservableObject:

    ReSwift Store: 23-28% CPU SwiftUI ObservableObject: 4-5% CPU

    Tested on iPad Pro, 11 inch, running iPadOS 16.1.1 (20B101).

    My app uses DisplayLink’s .onFrame to perform certain actions on each frame: https://github.com/timdonnelly/DisplayLink

    Interestingly, I had benchmarked this in ~Feb 2021 on ReSwift version 6.0.0 and had a much better CPU, e.g. ~6-7%.

    Currently, I get better perf on the older versions with StateType conformance:

    • 6.0.0: 16-19%
    • 5.0.0: 17-18%

    Is this expected, or am I using ReSwift incorrectly? What could account for such a big perf difference?

    Relevant code below. Also, repo with sample project: https://github.com/pianostringquartet/ReSwiftPerfTest/blob/main/PerfTestApp/ReSwiftPerfPlay.swift

    //
    //  ReSwiftPerfTest.swift
    //
    //  Created by Christian J Clampitt on 11/23/22.
    //
    
    import SwiftUI
    import ReSwift
    import DisplayLink
    
    //@main
    //struct MyApp: App {
    //    var body: some Scene {
    //        WindowGroup {
    //            ReSwiftPerfPlay()
    //        }
    //    }
    //}
    
    class ObservableData: ObservableObject {
        var counter = 0
    }
    
    struct ReSwiftState: StateType, Equatable {
        var counter = 0
    }
    
    struct ReSwiftPerfPlay: View {
    
        @StateObject var observableData = ObservableData()
        @StateObject var storeData = ReSwiftStore<ReSwiftState>(store: reswiftStore())
    
        var body: some View {
            Text("UI does not use state")
                // DisplayLink: on-frame callback:
                // https://github.com/timdonnelly/DisplayLink
                .onFrame { _ in
    
                    // When updating `observableData` alone,
                    // CPU is 4-5%
    //                observableData.counter += 1
    
                    // When updating `storeData` alone:
                    // ReSwift 6.1.0: CPU is 23-28%
                    // ReSwift 6.0.0: CPU is 16-19%
                    // ReSwift 5.0.0: CPU is 17-18%
                    storeData.dispatch(CounterIncremented())
                }
        }
    }
    
    // -- MARK: reducer, store, action
    
    func reswiftReducer(action: Action,
                        state: ReSwiftState?) -> ReSwiftState {
        var totalState = state ?? ReSwiftState()
    
        if action is CounterIncremented {
            totalState.counter += 1
            return totalState
        } else {
            return totalState
        }
    }
    
    struct CounterIncremented: Action, Equatable { }
    
    func reswiftStore() -> Store<ReSwiftState> {
        Store<ReSwiftState>(reducer: reswiftReducer,
                            state: ReSwiftState())
    }
    
    // -- MARK: adapting ReSwift to SwiftUI
    
    typealias Dispatch = (Action) -> Void
    
    class ReSwiftStore<T: StateType>: ObservableObject {
        private var store: Store<T>
    
        @Published var state: T
    
        let dispatch: Dispatch
    
        init(store: Store<T>) {
            self.store = store
            self.state = store.state
    
            let dispatch: Dispatch = store.dispatch
            self.dispatch = dispatch
    
            store.subscribe(self)
        }
    
        deinit {
            store.unsubscribe(self)
        }
    }
    
    extension ReSwiftStore: StoreSubscriber {
        public func newState(state: T) {
            DispatchQueue.main.async {
                self.state = state
            }
        }
    }
    
    
    opened by pianostringquartet 1
Releases(6.1.0)
  • 6.1.0(May 11, 2021)

  • 6.0.0(Oct 2, 2020)

    Breaking API Changes:

    • Drop support for Swift 3.2, 4.0, and 4.1. (#418) - @DivineDominion
    • Drop support for iOS 8 (#447) - @DominicSchiller-IBM-CIC

    API Changes:

    • Add capability to mutate Middleware after store creation. (#427) - @mjarvis

    Other:

    • Add key paths to subscription select (#415) - @djtech42
    • Make isDispatching of Store atomic (#341, #446) - @zhongwuzw, @basememara
    Source code(tar.gz)
    Source code(zip)
    ReSwift.framework.zip(4.46 MB)
  • 5.0.0(Jul 2, 2019)

    Breaking API Changes:

    • Remove StandardAction and StandardActionConvertible (#270) - @mjarvis

      • The existence of StandardAction and StandardActionConvertible is somewhat confusing to new users, and does not have a direct use case within the core ReSwift library. Therefore, it has been moved to ReSwift-Recorder where it belongs.
      • If you're using StandardAction in your project without ReSwift-Recorder, you can copy the old implementation into your project as a middle ground while you migrate away from its usage.
    • Make Store's state setter private (#354) - @mokagio

      • Removes the ability to directly set state by making it private(set). This prevents users from bypassing reducers and middleware. All mutation of the state must occur through the normal Action & Reducer methods.
      • This deprecates the usage of ReSwift-Recorder. Changes may be made to that library in the future in order to support this change.

    Other:

    • Resolve Xcode 10.2 warnings with Swift 4.2.2 and 5.0 (#397) - @mjarvis
    • Update Swift Package Manager support (#403, #411) - @Dschee, @hoemoon
    Source code(tar.gz)
    Source code(zip)
    ReSwift.framework.zip(1.94 MB)
  • 4.1.1(Mar 21, 2019)

  • 4.1.0(Mar 21, 2019)

    API Changes:

    • Deprecate StandardAction and StandardActionConvertible - @mjarvis

      • These have been moved to https://github.com/ReSwift/ReSwift-Recorder since they are unnecessary for the base use of ReSwift
    • Deprecate ActionCreator and AsyncActionCreator (#391) - @mjarvis

      • These are deprecated in favor of https://github.com/ReSwift/ReSwift-Thunk

    Other

    • Add Subscription skip(when:) and only(when:) (#242) - @mjarvis
    • Add automaticallySkipsRepeats configuration option to Store initializer (#262) - @DivineDominion
    • Improve subscription & state update performance (#325) - @mjarvis
    • Enable build settings "Allow app extension API only" (#328) - @oradyvan
    • Open Subscription<State> to allow external extensions (#383) - @mjarvis
    • Update project to Swift 4.2 (#256, #335, #374) - @mjarvis, @DivineDominion
    Source code(tar.gz)
    Source code(zip)
    ReSwift.framework.zip(2.25 MB)
  • 4.0.1(Dec 19, 2017)

    Other:

    • Fix retain cycle in SubscriptionBox (#278) - @mjarvis, @DivineDominion
    • Fix bug where using skipRepeats with optional substate would not notify when the substate became nil #55655 - @Ben-G
    • Add automatic skipRepeats for Equatable substate selection (#300) - @JoeCherry
    Source code(tar.gz)
    Source code(zip)
    ReSwift.framework.zip(1.63 MB)
  • 4.0.0(Apr 20, 2017)

    Breaking API Changes:

    • Introduced a new Subscription API (#203) - @Ben-G, @mjarvis, @DivineDominion

      • The subscription API provides basic operators, such as skipRepeats (skip calls to newState unless state value changed) and select (sub-select a state).

      • This is a breaking API change that requires migrating existing subscriptions that sub-select a portion of a store's state:

        • Subselecting state in 3.0.0:

          store.subscribe(subscriber) { ($0.testValue, $0.otherState?.name) }
          
        • Subselecting state in 4.0.0:

          store.subscribe(subscriber) {
            $0.select {
              ($0.testValue, $0.otherState?.name)
            }
          }
          
      • For any store state that is Equatable or any sub-selected state that is Equatable, skipRepeats will be used by default.

      • For states/substates that are not Equatable, skipRepeats can be implemented via a closure:

        store.subscribe(subscriber) {
          $0.select {
              $0.testValue
              }.skipRepeats {
                  return $0 == $1
              }
        }
        
    • Reducer type has been removed in favor of reducer function (#177) - Ben-G

      • Here's an example of a new app reducer, for details see the README:

        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
        }
        
    • dispatch functions now return Void instead of Any (#187) - @Qata

      • The return type has been removed without any replacement, since the core team did not find any use cases of it. A common usage of the return type in redux is returning a promise that is fullfilled when a dispatched action is processed. While it's generally discouraged to disrupt the unidirectional data flow using this mechanism we do provide a dispatch overload that takes a callback argument and serves this purpose.
    • Make dispatch argument in middleware non-optional (#225) - @dimazen, @mjarvis, @Ben-G

    • Middleware now has a generic type parameter that is used for the getState method and matches the Store's State type. This allows accessing the state in middleware code without type casting (#226) - @mjarvis

    Other:

    • Extend StoreType with substate selector subscription (#192) - @mjarvis
    • Add DispatchingStoreType protocol for testing (#197) - @mjarvis
    • Installation guide for Swift Package Manager - @thomaspaulmann
    • Update documentation to reflect breaking API changes - @mjarvis
    • Clarify error message on concurrent usage of ReSwift - @langford
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0(Nov 13, 2016)

    Released: 11/12/2016

    This release supports Swift 3.0.1

    Breaking API Changes:

    • Dropped support for Swift 2.2 and lower (#157) - @Ben-G

    API Changes:

    • Mark Store as open, this reverts a previously accidental breaking API Change (#157) - @Ben-G

    Other:

    • Update to Swift 3.0.1 - @Cristiam, @Ben-G
    • Documentation changes - @vkotovv
    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(Sep 16, 2016)

    Released: 09/15/2016

    This version supports Swift 3 for Swift 2.2 support use an earlier release.

    Other:

    • Swift 3 preview compatibility, maintaining Swift 2 naming - (#126) - @agentk
    • Xcode 8 GM Swift 3 Updates (#149) - @tkersey
    • Migrate Quick/Nimble testing to XCTest - (#127) - @agentk
    • Automatically build docs via Travis CI (#128) - @agentk
    • Documentation Updates & Fixes - @mikekavouras, @ColinEberhardt
    Source code(tar.gz)
    Source code(zip)
    ReSwift.framework.zip(2.00 MB)
  • 2.0.0(Jun 30, 2016)

    Released: 06/30/2016

    Breaking API Changes:

    • Significant Improvements to Serialization Code for StandardAction (relevant for recording tools) - @okla

    Other:

    • Swift 2.3 Updates - @agentk
    • Documentation Updates & Fixes - @okla, @gregpardo, @tomj, @askielboe, @mitsuse, @esttorhe, @RyanCCollins, @thomaspaulmann, @jlampa
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Mar 20, 2016)

    The first major release of ReSwift :tada: From here on forward we will be using semantic versioning.

    API Changes:

    • Remove callback arguments on synchronous dispatch methods - @Ben-G

    Other:

    • Move all documentation source into Docs, except Readme, Changelog and License - @agentk
    • Replace duplicated documentation with an enhanced generate_docs.sh build script - @agentk
    • Set CocoaPods documentation URL - (#56) @agentk
    • Update documentation for 1.0 release - @Ben-G
    Source code(tar.gz)
    Source code(zip)
  • 0.2.5(Feb 23, 2016)

    Released: 02/20/2015

    API Changes:

    • Subscribers can now sub-select a state when they subscribe to the store (#61) - @Ben-G
    • Rename initially dispatched Action to ReSwiftInit - @vfn

    Fixes:

    • Fix retain cycle caused by middleware (issue: #66) - @Ben-G
    • Store now holds weak references to subscribers to avoid unexpected memory managegement behavior (issue: #62) - @vfn
    • Documentation Fixes - @victorpimentel, @vfn, @juggernate, @raheelahmad

    Other:

    • We now have a hosted documentation for ReSwift - @agentk
    • Refactored subscribers into a explicit Subscription typealias - @DivineDominion
    • Refactored dispatch for AsyncActionCreator to avoid duplicate code - @sendyhalim
    Source code(tar.gz)
    Source code(zip)
  • 0.2.4(Jan 23, 2016)

    API Changes:

    • Pass typed store reference into ActionCreator. ActionCreator can now access Stores state without the need for typecasts - @Ben-G
    • Store can now be initialized with an empty state, allowing reducers to hydrate the store - @Ben-G

    Bugfixes

    • Break retain cycle when using middelware - @sendyhalim

    Other:

    • Update Documentation to reflect renaming to ReSwift - @agentk
    • Documentation fixes - @orta and @sendyhalim
    • Refactoring - @dcvz and @sendyhalim
    Source code(tar.gz)
    Source code(zip)
  • 0.2.3(Jan 23, 2016)

  • 0.2.2(Jan 16, 2016)

  • v0.2.1(Dec 31, 2015)

  • v0.2(Dec 29, 2015)

    This release comes with the following major changes:

    • Middleware Support
    • Typed Actions are now used by default instead of serializable Actions
    • Much API renaming

    Details:

    This Release Provides a Middleware API for Swift Flow. Further the library now uses typed actions by default. It is no longer required to provide actions that can be serialized. If you want to use Swift Flow Recorder, you can opt into serialization of actions.

    Source code(tar.gz)
    Source code(zip)
    SwiftFlow.framework.zip(1.23 MB)
  • v0.1(Dec 15, 2015)

πŸ”„ Unidirectional data flow in Swift.

Reactor Reactor is a framework for making more reactive applications inspired by Elm, Redux, and recent work on ReSwift. It's small and simple (just o

Reactor 175 Jul 9, 2022
A super simple library for state management with unidirectional data flow.

OneWay ?? OneWay is still experimental. As such, expect things to break and change in the coming months. OneWay is a super simple library for state ma

SeungYeop Yeom 41 Dec 20, 2022
Unidirectional flow implemented using the latest Swift Generics and Swift Concurrency features.

swift-unidirectional-flow Unidirectional flow implemented using the latest Swift Generics and Swift Concurrency features. struct SearchState: Equatabl

Majid Jabrayilov 104 Dec 26, 2022
MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner

CZInstagram MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner. Unidirectional Da

Cheng Zhang 56 Nov 1, 2022
Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux Introduction VueFlux is the architecture to manage state with unidi

Ryo Aoyama 324 Dec 17, 2022
πŸ€– RxSwift + State Machine, inspired by Redux and Elm.

RxAutomaton RxSwift port of ReactiveAutomaton (State Machine). Terminology Whenever the word "signal" or "(signal) producer" appears (derived from Rea

Yasuhiro Inami 719 Nov 19, 2022
Redux for Swift - a predictable state container for Swift apps

Merge / deprecation announcement: ReduxKit and Swift-Flow have joined forces! The result is ReSwift. The nitty gritty: We decided to deprecate ReduxKi

null 613 Jan 3, 2023
A barebone, thread-safe Redux-like container for Swift.

SwiftTinyRedux SwiftTinyRedux is a barebone, thread-safe Redux-like container for Swift. It features a minimal API and supports composable reducers. I

Valentin Radu 9 Nov 13, 2022
Sample iOS application in SwiftUI presenting Redux architecture

SwiftUI-Redux-Demo Sample iOS application in SwiftUI presenting Redux architecture. My full article about Redux in detail you will find here: Redux ar

Wojciech Kulik 25 Nov 27, 2022
πŸ’Ž Redux like architecture for SwiftUI

Simple Architecture like Redux Installation SPM dependencies: [ .package(url: "https://github.com/gre4ixin/ReduxUI.git", .upToNextMinor(from: "1.0

Pavel 38 Dec 13, 2022
Flux for SwiftUI, inspired by Vuex

⚠️ Fluxus is no longer maintained, and may not be using latest SwiftUI best practices. ?? I encourage you to look at the source of Fluxus. If you do,

John Susek 84 Jul 31, 2022
🌾 Harvest: Apple's Combine.framework + State Machine, inspired by Elm.

NOTE: This repository has been discontinued in favor of Actomaton. ?? Harvest Apple's Combine.framework (from iOS 13) + State Machine, inspired by Elm

Yasuhiro Inami 386 Dec 18, 2022
Core Data with ReactiveCocoa

ReactiveCoreData ReactiveCoreData (RCD) is an attempt to bring Core Data into the ReactiveCocoa (RAC) world. Currently has several files with the sour

Jacob Gorban 258 Aug 6, 2022
RxSwift extensions for Core Data

RxCoreData Example To run the example project, clone the repo, and run pod install from the Example directory first. Requirements Xcode 9.0 Swift 4.0

RxSwift Community 164 Oct 14, 2022
Two-way data binding framework for iOS. Only one API to learn.

BindKit A simple to use two-way data binding framework for iOS. Only one API to learn. Supports Objective-C, Swift 5, Xcode 10.2, iOS 8 and above. Shi

Electric Bolt 13 May 25, 2022
This Repository holds learning data on Combine Framework

Combine Framework List of Topics Welcome, every section in this repo contains a collection of exercises demonstrating combine's utilization as well as

Julio Ismael Robles 2 Mar 17, 2022
Binding - Data binding framework (view model binding on MVVM) written using propertyWrapper and resultBuilder

Binding Data binding framework (view model binding on MVVM) written using @prope

Sugeng Wibowo 4 Mar 23, 2022
Reactive Programming in Swift

Rx is a generic abstraction of computation expressed through Observable<Element> interface, which lets you broadcast and subscribe to values and other

ReactiveX 23.1k Jan 5, 2023
RxSwift extentions for Swift optionals and "Occupiable" types

RxOptional RxSwift extentions for Swift optionals and "Occupiable" types. Usage All operators are available on Driver as well unless otherwise marked.

Thane Gill 8 Jun 28, 2020