SwiftRex puts a Redux store inside SwiftUI without hiding the algebra
Swift + Redux + (SwiftConcurrency | Combine | RxSwift | ReactiveSwift | ReactiveConcurrency) -> SwiftRex
At a glance
- What is it?
- SwiftRex is a unidirectional dataflow framework for Swift from the SwiftRex organisation, where one Store owns the state, views dispatch actions and pure behaviors describe state changes and effects. The unusual part is that the same core runs on Swift Concurrency, Combine, RxSwift, ReactiveSwift or ReactiveConcurrency through optional package traits.
- Who is it for?
- SwiftRex suits Swift teams that want a single store with pure reducers and testable effects, especially SwiftUI apps that need granular state reads and want to keep their own async runtime. It does not suit teams who want a small, boring view model pattern, and it does not suit a codebase that has to stay on the 0.8.x line, since the current documentation targets Swift 6.3 and later while the last tagged releases are from 2022.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One Store, and everything else is a value
The design rule is stated in a single sentence. One Store owns your app's state, views dispatch actions, pure behaviors describe how state changes and which effects run, and the Store is the only thing that executes anything. The consequences of that rule are listed as five properties. There is one source of truth, one state tree and one store, read by views granularly so a view redraws only for what it reads. There is nowhere to hide a side effect, because everything but the Store is an inert, composable value. There are no races, because every event is an action processed in order on the main actor, synchronously in the same run loop. It is testable without mocks, since dependencies are plain closures and logic is pure functions. And it is modular, since features are written against their own small types and lifted into the app. The race claim deserves a moment, because it is the one that separates this from most view model arrangements. Ordering is not a convention that developers maintain, it is a property of the store.
A feature is a state, an action, a behavior and an environment
The README shows a complete feature in one screen, and the shape is worth copying because it is the unit of work. A feature is an enum with a State struct, an Action enum, an Environment struct and a static behavior function. The state is a plain value that is Sendable and Equatable, the action enumerates the events the feature handles including the asynchronous result of a load, and the environment holds the dependencies as a Sendable closure, here a fetchMovies closure returning a Result. The behavior is a pure function of action and state that returns a redux style description of what to do.
static func behavior() -> Behavior<Action, State, Environment> {
.handle { action, _ in
switch action {
case .onAppear:
.reduce { $0.isLoading = true }
.produce { ctx in Effect.task { .loaded(await ctx.environment.fetchMovies()) } }
case let .loaded(.success(movies)):
.reduce { state in
state.movies = movies
state.isLoading = false
}
case .loaded(.failure):
.reduce { $0.isLoading = false }
}
}
}The mechanism is a builder that composes a reduce action and a produce action. Reduce mutates the state, produce declares an effect, and the effect in this case is a task that awaits the environment closure and dispatches the loaded action back into the store. The result is that a feature holds no side effect at all. The dependency is injected, the work is described, and the store decides when to run it.
Views get a view store, the app keeps the real one
The view layer is a small adapter over the store, and the distinction between the two matters. The App struct creates the real Store once, at the entry point, and holds it as a plain let. The documentation is explicit that it is dispatched to and followed, never observed.
@main
struct MoviesApp: App {
static let environment = Movies.Environment(fetchMovies: { await API.live.movies() })
let store = Store(initial: Movies.initialState(with: ()), behavior: Movies.behavior(), environment: MoviesApp.environment)The generated Movies.view(store:environment:) call then makes a view store from that store, and the view keeps it for the life of the view. The SwiftUI product provides ViewStore, @OwnedStore, granular reads using Observation on iOS 17 and later and Combine below that, bindings and presentation support. Granular reads address the usual complaint about unidirectional frameworks in SwiftUI, where any state change redraws everything. In the example the list is built with viewStore.state.each on the movies key, so each row depends on its own movie. A feature is bound to its view with the @BoundTo property wrapper, which injects a viewStore typed to that feature's action and state.
Pick one async runtime, and the others are not downloaded
This is the part of SwiftRex that has changed most recently and the part most likely to matter to an adopting team. The core does not own an async runtime. Instead there are separate products for each option, and the third-party bridges are gated behind package traits of the same name that are off by default, so choosing one never downloads the others. The Swift Concurrency product gives Effect.task, Effect.throwingTask, .asyncSequence and asChannel on any AsyncSequence. The Combine product gives asEffect and asChannel on a Publisher, and makes stateStream a Publisher. The RxSwift, ReactiveSwift and ReactiveConcurrency products are the same bridge for Observable, for SignalProducer and Signal, and for ReactiveConcurrency's cold async and await native Publisher respectively. In Xcode the trait is ticked in the Traits column of Project, Package Dependencies. The rest of the product list covers the architecture layer with @Feature, @BoundTo, building a child's behavior and view from one Relay.Scope, and navigation reducers, plus an operators module and a testing module with TestStore that is for test targets only.
Installation is a Swift Package Manager dependency block
The requirements are stated in one line: Swift Package Manager, Swift 6.3 or later, macOS 13, iOS 16, tvOS 16, watchOS 9, visionOS, and Linux. The package declaration lists the products you depend on, and the traits entry shows how a bridge is opted into.
dependencies: [
.package(
url: "https://github.com/SwiftRex/SwiftRex.git",
from: "1.0.0",
traits: ["ReactiveConcurrency"] // only for a trait-gated bridge; omit otherwise
)
],
targets: [
.target(name: "MyApp", dependencies: [
.product(name: "SwiftRex", package: "SwiftRex"),
.product(name: "SwiftRex.SwiftConcurrency", package: "SwiftRex"),
.product(name: "SwiftRex.SwiftUI", package: "SwiftRex"),
.product(name: "SwiftRex.Architecture", package: "SwiftRex"),
]),The comment in the snippet makes the rule plain: specify the trait only for a trait-gated bridge and omit it otherwise. The architecture layer is a separate product, so a team that wants the @Feature and @BoundTo machinery opts into it explicitly rather than getting it by default. The repository also ships a Mintfile, a .swift-version file, a Package.resolved, a swiftformat configuration and a swiftlint configuration, which tells you the project is disciplined about tooling, and a BenchmarkSuite directory for measuring.
A custom licence and a release history that stopped in 2022
Two facts about the project state deserve attention before anyone adopts it. The first is the licence. The repository is recorded as using a custom licence that GitHub cannot classify, with the LICENSE file as the authority, while the README badge shows Apache 2.0. Those two statements are not the same thing, and for a framework embedded in an app's dependency graph the difference is worth resolving with the maintainers rather than assuming the badge settles it. This is not legal advice, it is a flag for whoever handles your compliance. The second is the release history. The most recent tagged releases listed are 0.8.12 from 2022-04-02, 0.8.11 from 2022-01-07 and 0.8.10 from 2022-01-06, with titles covering Xcode 13 support, CocoaPods fixes, ASAPScheduler and CombineRex effect middleware cancellation. The current documentation and package manifest point at 1.0.0 and Swift 6.3, so the 0.8.x tags describe an older line, and the last push to the main branch was on 2026-10-19. The code is moving and the tags are not, and the two describe different things.
Editorial conclusion
SwiftRex suits Swift teams that want a single store with pure reducers and testable effects, especially SwiftUI apps that need granular state reads and want to keep their own async runtime. It does not suit teams who want a small, boring view model pattern, and it does not suit a codebase that has to stay on the 0.8.x line, since the current documentation targets Swift 6.3 and later while the last tagged releases are from 2022. Before adopting it, read the DocC catalog and the algebra page, add the package with only the one bridge trait you need, and check the LICENSE file, because the repository licence is a custom one that GitHub cannot classify.
Frequently asked questions
What is SwiftRex?
SwiftRex is a Redux-style unidirectional dataflow framework for Swift in which one Store owns the app state, views dispatch actions, and pure behaviors describe how state changes and which effects run, with the Store as the only thing that executes anything.
Can SwiftRex be used with Combine or RxSwift as well as Swift Concurrency?
Yes. There are separate products for Swift Concurrency, Combine, RxSwift, ReactiveSwift and ReactiveConcurrency, and the three third-party bridges sit behind package traits that are off by default, so picking one never downloads the others.
How do I add SwiftRex to a Swift project?
Through Swift Package Manager, depending on the SwiftRex package from version 1.0.0 and on the products you need, such as SwiftRex, SwiftRex.SwiftConcurrency, SwiftRex.SwiftUI and SwiftRex.Architecture. The requirements are Swift 6.3 or newer and macOS 13, iOS 16, tvOS 16, watchOS 9, visionOS or Linux.
How do you test a SwiftRex feature?
The project states it is testable without mocks because dependencies are plain closures and logic is pure functions, and it ships a TestStore in the SwiftRex.Testing product, which is intended for test targets only.
What licence does SwiftRex use?
The repository licence is recorded as a custom licence that GitHub cannot classify, with the LICENSE file as the authority, while the README badge shows Apache 2.0, so the two should be reconciled with the maintainers before you depend on it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/swiftrex-swiftrex)Was this useful?