πŸ”„ Unidirectional data flow in Swift.

Overview

bitrise Carthage compatible Swift Package Manager compatible macOS

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 one file), so you can either use Carthage to stay up to date, or just drag and drop into your project and go. Or you can look through it and roll your own.

Reactor encourages unidirectional data flow from a single source of truthβ€”i.e., there is one global object responsible for managing application data, and all UI is derived and updated from it. This way your UI is always in sync with your data, and your data is sync with itself since there are not multiple copies of it floating around your app.

Architecture

                                                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                     β”‚                  β”‚
                                                     β”‚                  β”‚
                                                     β”‚     Command      β”‚
                                                 β”Œβ”€β”€β”€β”‚     (Async)      β”‚
                                                 β”‚   β”‚                  β”‚
                                                 β”‚   β”‚                  β”‚
                                                 β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                 β”‚
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                             β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  β”‚                             β”‚   β”‚                  β”‚
β”‚                  β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚   β”‚                  β”‚
β”‚                  │◀─────────   Event   β”œβ”€β”€β”€β”€β—€β”€β”€β”΄β”€β”€β”€β”€                  β”‚
β”‚                  β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚                  β”‚
β”‚                  β”‚                                 β”‚                  β”‚
β”‚       Core       β”‚                                 β”‚    Subscriber    β”‚
β”‚                  β”‚                                 β”‚                  β”‚
β”‚                  β”‚                                 β”‚                  β”‚
β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”     β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚                  β”‚
β”‚    β”‚ State β”‚     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€   State   β”œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β–Άβ”‚                  β”‚
β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚   β”‚                  β”‚
β”‚                  β”‚                             β”‚   β”‚                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                             β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                 β”‚
                                                 β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                                 β”‚   β”‚                  β”‚
                                                 β”‚   β”‚                  β”‚
                                                 └──▢│    Middleware    β”‚
                                                     β”‚                  β”‚
                                                     β”‚                  β”‚
                                                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

There are six objects in the Reactor architecture:

  1. The State object - A struct with properties representing application data.
  2. The Event - Can trigger a state update.
  3. The Core - Holds the application state and responsible for firing events.
  4. The Subscriber - Often a view controller, listens for state updates.
  5. The Command - A task that can asynchronously fire events. Useful for networking, working with databases, or any other asynchronous task.
  6. Middleware - Receives every event and corresponding state. Useful for analytics, error handling, and other side effects.

State

State is anything that conforms to State. Here is an example:

struct Player: State {
    var name: String
    var level: Int

    mutating func react(to event: Event) {
        switch event {
        case let _ as LevelUp:
            level += 1
        default:
            break
        }
    }
}

Here we have a simple Player model, which is state in our application. Obviously most application states are more complicated than this, but this is where composition comes into play: we can create state by composing states.

struct RPGState: State {
    var player: Player
    var monsters: Monsters

    mutating func react(to event: Event) {
        player.react(to: event)
        monsters.react(to: event)
    }
}

Parent states can react to events however they wish, although this will in most cases involve delegating to substates default behavior.

Side note: does the sight of mutating make you feel impure? Have no fear, mutating semantics on value types here are actualy very safe in Swift, and it gives us an imperative look and feel, with the safety of functional programming.

Events

We've seen that an Event can change state. What does an Event look like? In it's most basic form, an event might look like this:

struct LevelUp: Event {}

In other situations, you might want to pass some data along with the event. For example, in an application with more than one player we need to know which player is leveling up.

struct LevelUp: Event {
    var playerID: Int
}

For many events, generics work very nicely.

struct Update<T>: Event {
    var newValue: T
}

The Core

So, how does the state get events? Since the Core is responsible for all State changes, you can send events to the core which will in turn update the state by calling react(to event: Event) on the root state. You can create a shared global Core used by your entire application (my suggestion), or tediously pass the reference from object to object if you're a masochist.

In order to initialize your core, simply call the Core's constructor and pass in your initial state and any middleware (discussed later in this readme). Personally, I like to make my core a shared instance and namespace it inside an enum.

enum App {
  static let sharedCore = Core(state: RPGState(), middlewares: [
        ReachabilityMiddleware(),
        ErrorLogger(),
        AnalyticsMiddleware(),
        ])
}

Here is an example of a simple view controller with a label displaying our intrepid character's level, and a "Level Up" button.

class PlayerViewController: UIViewController {
    var core = App.sharedCore
    @IBOutlet weak var levelLabel: UILabel!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        core.add(subscriber: self)
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        core.remove(subscriber: self)
    }

    @IBAction func didPressLevelUp() {
        core.fire(event: LevelUp())
    }
}

extension ViewController: Reactor.Subscriber {
    func update(with state: RPGState) {
        levelLabel?.text = String(state.level)
    }
}

By subscribing and subscribing in viewDidAppear/viewDidDisappear respectively, we ensure that whenever this view controller is visible it is up to date with the latest application state. Upon initial subscription, the core will send the latest state to the subscriber's update function. Button presses forward events back to the core, which will then update the state and result in subsequent calls to update. (note: the Core always dispatches back to the main thread when it updates subscribers, so it is safe to perform UI updates in update.)

Commands

Sometimes you want to fire an Event at a later point, for example after a network request, database query, or other asynchronous operation. In these cases, Command helps you interact with the Core in a safe and consistent way.

struct CreatePlayer: Command {
    var session = URLSession.shared
    var player: Player

    func execute(state: RPGState, core: Core<RPGState>) {
        let task = session.dataTask(with: player.createRequest()) { data, response, error in
            // handle response appropriately
            // then fire an update back to the Core
            core.fire(event: AddPlayer(player: player))
        }
        task.resume()
    }
}

// to fire a command
core.fire(command: CreatePlayer(player: myNewPlayer))

Commands get a copy of the current state, and a reference to the Core which allows them to fire Events as necessary.

Middleware

Sometimes you want to do something with an event besides just update application state. This is where Middleware comes into play. When you create a Core, along with the initial state, you may pass in an array of middleware. Each middleware gets called every time an event is fired. Middleware cannot mutate the state, but it does get a copy of the state along with the event. Middleware makes it easy to add things like logging, analytics, and error handling to an application. It is great for monitoring event and state and triggering side effects (for example: looking for HTTP 401 errors and then presenting a login screen).

struct LoggingMiddleware: Middleware {
    func process(event: Event, state: State) {
        switch event {
        case _ as LevelUp:
            print("Leveled Up!")
        default:
            break
        }
    }
}

Installation

Follow the installation guides to integrate Reactorin your App.

Swift Package Manager

To integrate Reactor in your App using Swift Package Manager, specify it in your Package.swift file:

import PackageDescription

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

Carthage

To integrate Reactor in your App using Carthage, specify it in your Cartfile:

github "ReactorSwift/Reactor" ~> X.Y.Z

Run carthage update to build the framework and drag the built Reactor.framework into your Xcode project.

Comments
  • Making Reactor a organisation

    Making Reactor a organisation

    How about moving Reactor to an organisation? It would be easier to set up separate repositories for the design, the framework itself and a couple of examples. I would be more than happy to help.

    opened by thomaspaulmann 6
  • Use Bart's serial queue and consolidate publishing

    Use Bart's serial queue and consolidate publishing

    Pushing the work into a serial queue helps keep guarantees of completely processing events in-order. Simplifying the publishing into Subscription gives that concept weight and consistent use in the file.

    opened by timshadel 5
  • Proposed naming changes

    Proposed naming changes

    Developers should use State

    Their code should be clean, so make give developers the simple name to use when making their states conform to the required protocol.

    Rename StateType to State. This means that the thing formerly known as State is now ReactorState, which is fine since its an alias almost exclusively used inside Reactor code. As such, I don't think the user documentation should mention that detail.

    handle is not specific

    A State often changes when it receives an event. That change is in reaction to the event. Therefore, change the mutating function's name to react(to event: Event).

    Middleware shouldn't change anything in response to an event. Hint at the idea of "work without side effects" by making that function's name process(event: Event).

    opened by timshadel 4
  • Support synchronous reactions

    Support synchronous reactions

    This change allows for firing on the main thread to act synchronously. This helps avoid a number of race conditions and other problematic behavior that occurs when mixing inherently synchronous work (the main thread) with asynchronous behavior.

    Note: This is potentially a breaking change, as people may have logic that does not work properly once the reactions are synchronous and serial. Those were probably originally bugs anyway, but will become more evident with this change.

    opened by benjaminsnorris 3
  • core declaration code

    core declaration code

    Well, it's not an issue as such, but a question about the core for Reactor.

    I have looked at the example code on the GitHub repo and those are useful, thanks. The only thing I couldn't see was an example of how and where to declare the core.

    The example of var core = App.sharedCore seems to assume that the sharedCore has already been declared, but doesn't show the syntax of how to do so.

    An example of how to declare the core will be very useful. Thanks.

    question 
    opened by ghost 2
  • Use the selector when publishing state updates.

    Use the selector when publishing state updates.

    The current code allowed me to pass in a selector to filter the state before it's passed to my update function but the selector was never actually used. This change just invokes the selector (if there is one) on all state updates.

    opened by lynns 2
  • Fix for race condition

    Fix for race condition

    We ran into a race condition where a viewController can subscribe upon loading, update and before the subscription gets added to the subscription list, an event can be fired with an update. The result is that the ViewController doesn't get updated for that event. In our case, we are blocking the UI while we fire a command to tell us what segue to fire and that command is finishing before the viewController is added to the list of subscriptions in certain conditions, leaving the ViewController indefinitely frozen. From our testing, this fix should rectify that problem.

    opened by patch-benjamin 1
  • Fix threading issues

    Fix threading issues

    The extra queue protecting the subscriptions was almost always wrapped by the jobQueue, and when it wasn't it should have been. The jobQueue is now the sole way to order requests, and the logic is much simpler.

    opened by timshadel 0
  • Namespacing Fix - Rename to Core

    Namespacing Fix - Rename to Core

    One of the problems Reactor is facing is due to how Swift handles importing. Specifically, if you are importing Reactor, and already have a local type named Event, Command, State, etc., then there is no way to tell Swift which one you are talking about.

    Normally, you could prefix the type's name with it's package (i.e. Reactor.State or MyApp.State), but since the package name and the class name are Reactor, when you import Reactor, you can not actually refer to Reactor's types by with their namespaces because the compiler thinks you're talking about Reactor the class, which has no internal types of Event, State, etc.

    There are really two options available: rename the package, or rename the Reactor class. This PR follows the latter proposing that the Reactor class be renamed Core. I'm not a nuclear physicist, so the analogy may not be 100% there, but it makes sense to me. According to wikipedia the core is where all the reactions actually take place, so I think it is an appropriate name.

    opened by jarsen 0
  • Let Commands wait for state to execute or expire

    Let Commands wait for state to execute or expire

    The intent is to make it easy to issue several back-to-back commands that implicitly depend on each other before they can run. GetLocation and GetWeather are an example. You need to know the location before you can request the weather. By issuing them both at the same time, your location command can see that the existing location is recent enough, and choose to do nothing in it's execute(state:, core:) function, and the weather command will still run properly since it will find location in the proper place in state.

    • Existing commands will be unaffected. Defaults always assume commands will run.
    • Any command that implements canExecute(state:) will default to expiring in 10 seconds.
    • Commands may never run if you stop sending events to core, but if that happens your app is lifeless, so in practice this shouldn't be a problem...I think.
    opened by timshadel 0
Releases(0.9)
Owner
Reactor
Unidirectional data flow Architecture in Swift.
Reactor
Unidirectional Data Flow in Swift - Inspired by Redux

ReSwift 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

null 7.3k Jan 3, 2023
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
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
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
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
A Swift Reactive Programming Kit

ReactiveKit is a lightweight Swift framework for reactive and functional reactive programming that enables you to get into the reactive world today. T

Declarative Hub 1.2k Dec 29, 2022
RxSwift wrapper around the elegant HTTP networking in Swift Alamofire

RxAlamofire RxAlamofire is a RxSwift wrapper around the elegant HTTP networking in Swift Alamofire. Getting Started Wrapping RxSwift around Alamofire

RxSwift Community 1.6k Jan 3, 2023
An array class implemented in Swift that can be observed using ReactiveCocoa's Signals

ReactiveArray An array class implemented in Swift that can be observed using ReactiveCocoa's Signals. Installation Carthage Add the following to your

Wolox 53 Jan 29, 2022
Simple and lightweight Functional Reactive Coding in Swift for the rest of us

The simplest Observable<T> implementation for Functional Reactive Programming you will ever find. This library does not use the term FRP (Functional R

Jens Ravens 1.1k Jan 3, 2023
Predictable state container for Swift too

ReduxSwift ReduxSwift is a minimal Swift port of Redux, a popular JavaScript library for application state management. Functionality Centralized State

Lucas Sunsi Abreu 38 Oct 6, 2020
Aftermath is a stateless message-driven micro-framework in Swift

Aftermath is a stateless message-driven micro-framework in Swift, which is based on the concept of the unidirectional data flow architecture.

HyperRedink 70 Dec 24, 2021