Reactor alternatives and similar libraries
Based on the "Reactive Programming" category.
Alternatively, view Reactor alternatives based on common mentions on social networks and blogs.
-
OpenCombine
Open source implementation of Apple's Combine framework for processing values over time. -
Tokamak
DISCONTINUED. SwiftUI-compatible framework for building browser apps with WebAssembly and native apps for other platforms [Moved to: https://github.com/TokamakUI/Tokamak] -
Katana
DISCONTINUED. Swift Apps in a Swoosh! A modern framework for creating iOS apps, inspired by Redux. -
Interstellar
DISCONTINUED. Simple and lightweight Functional Reactive Coding in Swift for the rest of us. :large_orange_diamond: -
Verge
π£ A robust Swift state-management framework designed for complex applications, featuring an integrated ORM for efficient data handling. -
VueFlux
:recycle: Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux -
RxReduce
DISCONTINUED. Lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way. -
LightweightObservable
π¬ A lightweight implementation of an observable sequence that you can subscribe to. -
ReactiveArray
An array class implemented in Swift that can be observed using ReactiveCocoa's Signals -
STDevRxExt
STDevRxExt contains some extension functions for RxSwift and RxCocoa which makes our live easy. -
RxAlamoRecord
RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with API's easier than ever reactively.
CodeRabbit: AI Code Reviews for Developers
* Code Quality Rankings and insights are calculated and provided by Lumnify.
They vary from L1 to L5 with "L5" being the highest.
Do you think we are missing an alternative of Reactor or a related project?
README
[](Logo/Logo.png)
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:
- The
State
object - A struct with properties representing application data. - The
Event
- Can trigger a state update. - The
Core
- Holds the application state and responsible for firing events. - The
Subscriber
- Often a view controller, listens for state updates. - The
Command
- A task that can asynchronously fire events. Useful for networking, working with databases, or any other asynchronous task. 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 Reactor
in 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.