I thought it prudent to start an issue which can describe the necessary steps forward for releasing a SwiftUI compatible version of ReSwift.
At this time, I have a port of ReSwift in mjarvis/swiftui which conforms StoreType to ObservableObject. This allows for one to use @ObservedObject or @EnvironmentObject property wrappers to access a store, allowing direct usage of store.state.xyz in view bodies.
There are a number of concerns that need to be taken care of before this branch can be released in a supported manner.
We can split off individual issues and a milestone for completing the work below after #1 is agreed upon.
Is my choice of implementation ideal? Perhaps there are other options than making StoreType: ObservableObject.
Maybe something around calling subscribe to create a publisher from an injected store?
Do others have suggestions? Or just need to get approvals for existing implementation.
We need to determine how we will include this functionality in a general ReSwift release. It is important that we maintain backwards compatibility for UIKit, and allow for bug fixes / feature release to continue for both paths.
Some general performance testing needs to be done to validate that this is an okay solution for a production environment.
Questions arise such as: How many/often state changes can occur before we overwhelm the SwiftUI diffing? Do we need to do some sort of diffing ourselves before?
Documentation and examples need to be split and updated to account for UIKit vs SwiftUI paths.
I am quite new to Swift but I am not new to Redux. I used it in other languages.
Seen from other frameworks and seen from functional reactive programming pattern which combines a iterator and a subscribable it should be fine IMHO. You can always have a look at this: https://redux.js.org/recipes/usage-with-typescript
I have not used the new SwiftUI states... my question is if any other thing like ReactiveSwift RxSwift can be wrapped around the SwiftUI. (https://github.com/ReactiveX/RxSwift) Having it decoupled means creater flexibility and longevity.
I owe you authors (and of the original source code) a lot. I just finished my app with ease.
I was not sure you were using this already:
https://github.com/ReactiveX/RxSwift
https://github.com/RxSwiftCommunity/RxState
I just read over Apple's Documentation (https://developer.apple.com/documentation/swiftui/state). Your approach should be fine I guess with using their state handling and add-on the reducer stuff. As I understand it, Apples implementation of state is optional. So I as a developer would like still to setThings over functions... as setHidden(true/false)... or something.
Sorry, I wrote a bit more. I wanted to praise a bit of your work with a comment.
Correct me if I am wrong in some assumptions.
If we think about ReSwift as Redux, and as SwiftUI as React, it becomes clear that the code that glues both together should live in a different package, as we already did for ReSwift-Thunk. It would be the equivalent of https://react-redux.js.org/
I would only touch this main package to improve performance, fix bugs, improve the API, types, add more tests and maybe change its implementation using more modern Swift (in case it's necessary, of course).
In summary, I think the core package should contain the store, the reducers (I think a better way of composing reducers could be neat), and the middleware layer, and everything else on top must happen in opt-in, third party libs.
@dani-mp Your suggestion to make reducer composition nicer sounds interesting. You could create an issue with "help wanted" and "discussion needed" and propose something for others to pick up (including me eventually :))
@dani-mp Thats a good point. Creating a separate library, perhaps ReSwift-SwiftUI which contains this glue?
I'll look into how that would be possible, and if it would have the same kind of performance as the store refactor that I've done in my branch.
@DivineDominion done, sir: https://github.com/ReSwift/ReSwift/issues/457
@mjarvis I don't know much about SwiftUI because I mostly work with JavaScript and ClojureScript these days (although I still have a small tvOS project in production where I use ReSwift), but if for some reason, and to play nicely with SwiftUI, the API should expose any other kind of low-level primitives (thinking about Combine, for instance) instead of the current subscription, I think that'd be ok as long as people not using SwiftUI could use them as well from normal UIKit/AppKit code. AFAIK, react-redux uses a set of wrappers (HOCs and hooks) on top of the original Redux's listeners implementation, which I believe are similar to our subscription-based mechanism here.
Here is a minimal SwiftUI "glue" that I've managed to get working:
protocol ObservableStoreType: DispatchingStoreType, ObservableObject {
associatedtype State: StateType
/// The current state stored in the store.
var state: State! { get }
}
class ObservableStore<State: StateType>: Store<State>, ObservableStoreType {
override func _defaultDispatch(action: Action) {
objectWillChange.send()
super._defaultDispatch(action: action)
}
}
Usage:
import SwiftUI
import ReSwift
protocol HasName {
var name: String { get }
}
struct ChangeName: Action {
let to: String
}
func nameReducer(action: Action, state: String?) -> String {
switch action {
case let action as ChangeName:
return action.to
default:
return state ?? ""
}
}
struct ContentView<S: ObservableStoreType>: View where S.State: HasName {
@ObservedObject var store: S
var body: some View {
VStack {
Text(store.state.name)
TextField("Name", text: Binding(
get: { self.store.state.name },
set: { self.store.dispatch(ChangeName(to: $0)) }
))
}
}
}
struct ContentView_Previews : PreviewProvider {
private struct MockState: StateType, HasName {
var name: String = "mock"
}
private static func mockReducer(action: Action, state: MockState?) -> MockState {
return MockState(
name: nameReducer(action: action, state: state?.name)
)
}
static var previews: some View {
ContentView(store: ObservableStore(reducer: mockReducer, state: MockState()))
}
}
This seems pretty nice. Simple minimal library, just need to replace Store(...) with ObservableStore(...) to get all the SwiftUI compatibility.
I'd like to figure out a way to use environment objects to pass the store around while maintaining the ability to keep views generic, but so far its feeling more awkward to go that route.
I've attached my small sample project. The relevant code is in SceneDelegate.swift and ContentView.swift.
ReSwiftUI.zip
Alternative, which allows for environment object and some nicer generic forms:
(omitting Subscriber and AnyObservableStore implementation details, but I have this working, see attached zip)
struct ContentView: View {
var body: some View {
Subscriber { (state: HasName, dispatch: @escaping DispatchFunction) in
VStack {
Text(state.name)
TextField("Name", text: Binding(
get: { state.name },
set: { dispatch(ChangeName(to: $0)) }
))
}
}
}
}
Preview replaced with:
static var previews: some View {
ContentView()
.environmentObject(
AnyObservableStore(store: ObservableStore(reducer: mockReducer, state: MockState()))
)
}
The biggest downside with this approach is there is no compile-time safety on the state: HasName conformance at the Subscriber { ... call. One could put any type here and it will compile, but then crash at runtime. I'm not sure I can avoid this with the type-erasure required.
Hi @mjarvis, thank you for your work!
I鈥檓 unfortunately unable to compile my project using your mjarvis/swiftui branch and the sample code from ReSwiftUI-StoreSubscriber.zip
I get the following build error:
Type 'ObservableStore<State>' does not conform to protocol 'ObservableStoreType'
Full code here: https://github.com/pianostringquartet/prototype/blob/add-reswift/prototype/ContentView.swift

Note: I can run the ReSwiftUI-StoreSubscriber project locally and, working off the main ReSwift branch, I can also locally run the approach described here: https://github.com/ReSwift/ReSwift/issues/424#issuecomment-532050246)
EDIT: I'm using XCode 12, Swift 5, and SwiftUI (relevant code from your SceneDelegate.swift put instead in ContentView.swift).
@pianostringquartet ReSwiftUI-StoreSubscriber.zip should work using the latest release of ReSwift, rather than my SwiftUI branch.
For reference: My plan this week is to try to implement a shim that mixes the ideas above, while also resolving the issue in #461
My rules are:
Here are some possible interfaces that I'll explore (note: pseudocode, these do not compile)
struct MyView<S: PublishingStoreType>: View where S.State: HasTitle {
let store: S
var body: some View {
Subscriber(store.statePublisher.map(\.title).removeDuplicates()) { title in
Text(title)
}
}
}
struct MyView<S: PublishingStoreType>: View where S.State: HasTitle {
let store: S
var body: some View {
store.subscribe {
$0.map(\.title).removeDuplicates()
} content: { title in
Text(title)
}
}
}
struct MyView<S: PublishingStoreType>: View where S.State: HasTitle {
let store: DispatchingStoreType
@ObservableObject var state: String
init(store: S) {
self.store = store
self._state = store.statePublisher.map(\.title).removeDuplicates()
}
var body: some View {
Text(state)
}
}
struct MyView<S, SubState>: SubscribingView<S, SubState> where S.State: HasTitle {
let selector = { state in
state.map(\.title).removeDuplicates()
}
@ViewBuilder func body(state: SubState) -> some View {
Text(state)
}
}
Option 1 seems needlessly complicated with the subscriber view type needing to be explicitly known.
Option 2 looks the closest to existing ReSwift code, which seems like a very good thing.
Options 3 and 4 are nice in that they make the actual body code very clear, but add a lot of complexity elsewhere.
At this time option 2 seems like the best choice for me to experiment with.
I can also see passing dispatch into content: alongside state in order to avoid having to use self.store.dispatch(...)
All options may also allow for removal of explicit .removeDuplicates() by re-introducing automaticallySkipsRepeats functionality on Equatable substate selections.
All options I can see having an extremely nice way of isolating the view itself from subscription code, by having a raw, private view which takes the state directly: (Example here using option 2 above)
struct MyView<S: PublishingStoreType>: View where S.State: HasTitle {
let store: S
var body: some View {
store.subscribe {
$0.map(\.title).removeDuplicates()
} content: { title in
_MyView(title: title)
}
}
}
private struct _MyView: View {
let title: String
var body: some View {
Text(title)
}
}
This isolation has numerous benefits including testability, mocking, view/state replacement, etc. (And just raw clean readability)
Latest rendition: ReSwiftUI-PublishingStore.zip
Example View:
struct ContentView<S: PublishingStoreType>: View where S.State: HasName {
let store: S
var body: some View {
store.subscribe(\.name) { name in
VStack {
Text(name)
TextField("Name", text: Binding(
get: { name },
set: { self.store.dispatch(ChangeName(to: $0)) }
))
Button(action: { self.store.dispatch(ChangeName(to: "Test")) }) {
Text("Update")
}
}
}
}
}
This includes functionality that supports automaticallySkipsRepeats on Equatable substate. (Update button included to tap to see in console that the onReceive is skipped)
Also available is an alternate func subscribe which takes a second transform: parameter, allowing one to manually transform the substate publisher. This alternative bypasses automaticallySkipsRepeats, allowing one to manually remove duplicates / filter updates, etc for more control over when updates come through.
Working on setting up https://github.com/ReSwift/ReSwift-SwiftUI which will start with the bindings from my last comment.
Once this is up and running all discussion + issues will move there.
We'll update this repo's README to include a link there once available.
After some riffing, heres another very minimal option:
class Subscriber<Value>: ObservableObject, StoreSubscriber {
@Published private(set) var value: Value!
init<S: StoreType>(_ store: S, transform: @escaping (ReSwift.Subscription<S.State>) -> ReSwift.Subscription<Value>) {
store.subscribe(self, transform: transform)
}
init<S: StoreType>(_ store: S, transform: @escaping (ReSwift.Subscription<S.State>) -> ReSwift.Subscription<Value>) where Value: Equatable {
store.subscribe(self, transform: transform)
}
func newState(state: Value) {
value = state
}
}
struct ContentView<S: StoreType>: View where S.State: HasName {
private let store: S
@ObservedObject private var name: Subscriber<String>
init(store: S) {
self.store = store
name = Subscriber(store) { $0.select(\.name) }
}
var body: some View {
VStack {
Text(name.value)
TextField("Name", text: Binding(
get: { name.value },
set: { store.dispatch(ChangeName(to: $0)) }
))
Button(action: { store.dispatch(ChangeName(to: "Test")) }) {
Text("Update")
}
}
}
}
This seems more like idiomatic SwiftUI to me, more similar to the @FetchRequest type system for core data (A friend and I both tried to replicate something closer to that to integrate the subscribe into the property wrapper, but unfortunately apple does not publicly expose the capabilities for a property wrapper to indicate for the view to re-render)
This is also much cleaner glue -- Not even an extension on Store, just a simple observable object that deals with the subscription. The initializers are even not necessary -- I've included them only to force the user to subscribe immediately, rather than allow for a runtime error if one neglected to subscribe via the store.
The ideal situation here if the capabilities were added would be able to do something like @StoredState(\.name) var name: String and have the property wrapper subscribe + notify the view, but as mentioned above, we can't notify the view, and passing the store into this option would be quite awkward.
One downside to this option is it moves the subscriptions up to the root of the view, which means if we have multiple subscriptions, any one changing causes the entire view's tree to be diff'd by SwiftUI, whereas the other, inline subscribe functionality would reduce the diffing down to only the content within the subscription, but, as mentioned, this is how they intend for things to work with core data, etc. so maybe its an okay downside?
Most helpful comment
Working on setting up https://github.com/ReSwift/ReSwift-SwiftUI which will start with the bindings from my last comment.
Once this is up and running all discussion + issues will move there.
We'll update this repo's README to include a link there once available.