A lightweight Elm-like Store for SwiftUI

Overview

ObservableStore

A simple Elm-like Store for SwiftUI, based on ObservableObject.

ObservableStore helps you craft more reliable apps by centralizing all of your application state into one place, and making all changes to state deterministic. If you’ve ever used Elm or Redux, you get the gist. All state updates happen through actions passed to an update function. This guarantees your application will produce exactly the same state, given the same actions in the same order.

Because Store is an ObservableObject, it can be used anywhere in SwiftUI that ObservableObject would be used.

Store is meant to be used as part of a single app-wide, or major-view-wide component. It deliberately does not solve for nested components or nested stores. Following Elm, deeply nested components are avoided. Instead, it is designed for apps that use a single store, or perhaps one store per major view. Instead of decomposing an app into many stateful components, ObservableStore favors decomposing an app into many stateless views that share the same store and actions. Sub-views can be passed data through bare properties of store.state, or bindings, which can be created with store.binding, or share the store globally, through EnvironmentObject. See https://guide.elm-lang.org/architecture/ and https://guide.elm-lang.org/webapps/structure.html for more about this philosophy.

Example

A minimal example of Store used to increment a count with a button.

import SwiftUI
import Combine
import ObservableStore

/// Actions
enum AppAction {
    case increment
}

/// Services like API methods go here
struct AppEnvironment {
}

/// App state
struct AppState: Equatable {
    var count = 0

    /// State update function
    static func update(
        state: AppState,
        action: AppAction,
        environment: AppEnvironment
    ) -> Update<AppState, AppAction> {
        switch action {
        case .increment:
            var model = state
            model.count = model.count + 1
            return Update(state: model)
        }
    }
}

struct AppView: View {
    @StateObject var store = Store(
        update: AppState.update,
        state: AppState(),
        environment: AppEnvironment()
    )

    var body: some View {
        VStack {
            Text("The count is: \(store.state.count)")
            Button(
                action: {
                    // Send `.increment` action to store,
                    // updating state.
                    store.send(.increment)
                },
                label: {
                    Text("Increment")
                }
            )
        }
    }
}

State, updates, and actions

A Store is a source of truth for application state. It's an ObservableObject, so you can use it anywhere in SwiftUI that you would use an ObservableObject—as an @ObservedObject, a @StateObject, or @EnvironmentObject.

Store exposes a single @Published property, state, which represents your application state. state is read-only, and cannot be updated directly. Instead, like Elm or Redux, all state changes happen through a single update function, with the signature:

(State, Action, Environment) -> Update<State, Action>

The Update returned is a small struct that contains a new state, plus any optional effects and animations associated with the state transition (more about that in a bit).

state can be any Equatable type, typically a struct. Before setting a new state, Store checks that it is not equal to the previous state. New states that are equal to old states are not set, making them a no-op. This means views only recalculate when the state actually changes. Additionally, because state is Equatable, you can make any view that relies on Store, or part of Store, an EquatableView, so the view’s body will only be recalculated if the values it cares about change.

Getting and setting state in views

There are a few different ways to work with Store in views.

Store.state lets you reference the current state directly within views. It’s read-only, so this is the approach to take if your view just needs to read, and doesn’t need to change state.

Text(store.state.text)

Store.send(_) lets you send actions to the store to change state. You might call send within a button action, or event callback, for example.

Button("Set color to red") {
    store.send(AppAction.setColor(.red))
}

Store.binding(get:tag:) lets you create a binding that represents some part of the state. A get function reads the state into a value, a tag function turns a value set on the binding into an action. The result is a binding that can be passed to any vanilla SwiftUI view, yet changes state only through deterministic updates.

TextField(
    "Username"
    text: store.binding(
        get: { state in state.username },
        tag: { username in .setUsername(username) }
    )
)

Or, shorthand:

TextField(
    "Username"
    text: store.binding(
        get: \.username,
        tag: .setUsername
    )
)

You can also create bindings for sub-properties, just like with any other SwiftUI binding. Here's an example of creating a binding to a deep property of the state:

TextField(
    "Bio"
    text: store
        .binding(
            get: { state in state.settings },
            tag: { settings in .setSettings(settings) }
        )
        .profile
        .bio
)

Bottom line, because Store is just an ordinary ObservableObject, and can produce bindings, you can write views exactly the same way you write vanilla SwiftUI views. No special magic! Properties, @Binding, @ObservedObject, @StateObject and @EnvironmentObject all work as you would expect.

Effects

Updates are also able to produce asyncronous effects via Combine publishers. This lets you schedule asyncronous things like HTTP requests or database calls in response to actions. Using effects, you can model everything via a deterministic sequence of actions, even asyncronous side-effects.

Effects are modeled as Combine Publishers which publish actions and never fail.

For convenience, ObservableStore defines a typealias for effect publishers:

public typealias Fx<Action> = AnyPublisher<Action, Never>

The most common way to produce effects is by exposing methods on Environment that produce effects publishers. For example, an asyncronous call to an authentication API service might be implemented in Environment, where an effects publisher is used to signal whether authentication was successful.

struct Environment {
    // ...
    func authenticate(credentials: Credentials) -> AnyPublisher<Action, Never> {
      // ...
    }
}

You can subscribe to an effects publisher by returning it as part of an Update:

func update(
    state: State,
    action: Action,
    environment: Environment
) -> Update<State, Action> {
    switch action {
    // ...
    case .authenticate(let credentials):
        return Update(
            state: state,
            fx: environment.authenticate(credentials: credentials)
        )
    }
}

Store will manage the lifecycle of any publishers returned by an Update, piping the actions they produce back into the store, producing new states, and cleaning them up when they complete.

Animations

You can also drive explicit animations as part of an Update.

Use Update.animation to set an explicit Animation for this state update.

func update(
    state: State,
    action: Action,
    environment: Environment
) -> Update<State, Action> {
    switch action {
    // ...
    case .authenticate(let credentials):
        return Update(state: state).animation(.default)
    }
}

When you specify a transition or animation as part of an Update thisway, Store will use it when setting the state for the update.

Comments
  • Synthesize `update(state:actions:environment:)` for ModelProtocol

    Synthesize `update(state:actions:environment:)` for ModelProtocol

    #15 caused fx to be run immediately instead of joined on main. The intent was to allow for composing multiple actions by sending up many Just(.action) fx. However,

    • This is verbose to write, and rather "chatty".
    • It also makes the store implementation less straightforward, since without joining on main, we must check if fx was completed immediately before adding to fx dictionary. Joining on main solves this problem by running the fx on next tick, after the fx has been added to dictionary.
    • Additionally, it means that off-main-thread fx are required to be joined manually on main to prevent SwiftUI from complaining.

    This PR introduces an alternative approach to composing multiple update functions. Any type that conforms to ModelProtocol has a update(state:actions:environment) static function synthesized for it. This function can be used to simulate the effect of sending multiple actions in sequence immediately, in effect, composing the actions.

    update(
        state: state,
        actions: [
            .setEditor(
                text: detail.entry.body,
                saveState: detail.saveState
            ),
            .presentDetail(true),
            .requestEditorFocus(false)
        ],
        environment: environment
    )
    

    State is updated immediately, fx are merged, and last transaction wins.

    Now that we have a way to immediately sequence actions in same state update, we no longer need to run fx on same tick. Joining on main is my preference from an API perspective because it has fewer footguns in implementation and use.

    Breaking changes

    • Remove Update.pipe. Redundant now. Was never happy with it anyway. It was an inelegant way to accomplish the same thing as update(state:actions:environment:).
    • Revert fx to be joined on main thread. We join on main with a .default QoS, because fx should be async/never block user interaction.
    opened by gordonbrander 0
  • Introduce ViewStore for scoped component stores

    Introduce ViewStore for scoped component stores

    This PR introduces the ability to create scoped stores for sub-components, called ViewStores. ViewStores are conceptually like bindings for stores, except that they expose the store API, instead of a binding API. This approach is inspired by the component mapping pattern from Elm.

    We also update the implementation of Store so that fx are run immediately instead of being joined on main queue. This is to avoid adding delay when intercepting child actions and then sending them back up as fx.

    Changes

    • Introduce ModelProtocol, which implements an update(state:action:environment) static function.
      • Model protocol allows us to treat action and environment as associatedtypes, which simplifies many of our other type signatures.
    • Introduce StoreProtocol, a protocol that describes a kind of store.
    • Introduce ViewStore<Model: ModelProtocol>, which implements StoreProtocol
    • Implement StoreProtocol for Store
    • Introduce CursorProtocol, which describes how to map from one domain to another, and provides a convenience function for updating child components.
    • Replace .binding with generic Binding intializer for any StoreProtocol
    • Add new tests

    Breaking changes

    • Fx are now run immediately, meaning you will have to manually join asyncronous fx to main queue with .receive(on: DispatchQueue.main)
    • Store requires that state implement ModelProtocol. This allows us to simplify the signatures of many other APIs
      • Update type signature changes from Update<State, Action> to Update<Model: ModelProtocol>.
      • Store type signature changes from Store<State, Action, Environment> to Store<Model: ModelProtocol>
      • Store initializer changes from Store.init(update:state:environment:) to Store.init(state:environment:). Now that state conforms to ModelProtocol, you don't have to explicitly pass in the update function as a closure. We can just call the protocol implementation.
    • store.binding has been removed in favor of Binding(store:get:send:)
    opened by gordonbrander 0
  • Reorder update fn signature to State, Action, Environment

    Reorder update fn signature to State, Action, Environment

    This PR changes the order of update function arguments:

    • Changes update function signature from update(State, Environment, Action) to update(State, Action, Environment)
    • Changes Store from Store<State, Environment, Action> to Store<State, Action, Environment>

    Why?

    • This order of arguments is used by two other Elm-like libraries: Swift Composable Architecture and Redux-like state container.
    • Context arguments like environment often seem to be passed last in Swift. E.g. UIViewRepresentable.Context is passed last to methods in SwiftUI.
    • State and Action are always used, but Environment might be nil, or an empty struct. You don't always need environment. It is optional.
    • The order reads better in the signature, since state and action are always paired together in update function and Update struct.
    opened by gordonbrander 0
  • Create binding form that does not set animation

    Create binding form that does not set animation

    Previously if no animation was passed to binding, we would send withAnimation(nil). However, we don't want to set withAnimation(nil) unless explicitly asked, as this may override withAnimation called elsewhere.

    Instead, we now introduce two forms of Store.binding:

    Store.binding(get:tag:)
    Store.binding(get:tag:animation:)
    

    The first form does not call withAnimation, leaving the transaction state alone.

    opened by gordonbrander 0
  • Update arg labels for send, subscribe

    Update arg labels for send, subscribe

    Fixes #7

    Per https://www.swift.org/documentation/api-design-guidelines/#argument-labels,

    If the first argument forms part of a grammatical phrase, omit its label.

    This is the case for store.send(_), so we remove the label.

    When the first argument forms part of a prepositional phrase, give it an argument label.

    We subscribe to a publisher, therefore store.subscribe(to:)

    opened by gordonbrander 0
  • Introduce `mergeFx`

    Introduce `mergeFx`

    Fixes #8.

    This PR also removes Update.transaction(_) since name conflicts with Update.transaction property. Note I decided against adding fx for the same reason. I will avoid adding plain old setters for now, since chaining sets is not really the most common need.

    If we find we really need more than property setters for simple sets in the future, we may want to add a map function instead.

    opened by gordonbrander 0
  • Deprecate send(action:) in favor of send(_)

    Deprecate send(action:) in favor of send(_)

    The action label is redundant, and unnecessary vis Swift's own style guide https://www.swift.org/documentation/api-design-guidelines/#argument-labels.

    Consider removing.

    enhancement 
    opened by gordonbrander 0
  • Receive Fx on main thread

    Receive Fx on main thread

    Receive Fx on main thread. This does two important things:

    First, SwiftUI requires that any state mutations that would change views happen on the main thread. Receiving on main ensures that all fx-driven state transitions happen on main, even if the publisher is off-main-thread.

    Second, if we don’t schedule to receive on main, it is possible for publishers to complete immediately, causing receiveCompletion to attempt to remove the publisher from cancellables before it is added. This was happening with Empty() publishers returned by default when you do not specify Update(fx:).

    By scheduling to receive publisher on main, we force publisher to complete on next tick, ensuring that it is always first added, then removed from cancellables.

    This is an alternative to the flag-based approach in https://github.com/gordonbrander/ObservableStore/blob/2022-03-12-fx-tests-2/Sources/ObservableStore/ObservableStore.swift#L175.

    More background

    Cancellables will cancel themselves they are released (makes sense). This means you must keep cancellables alive for the duration of the lifetime of the publisher by storing them somewhere. In our case, we store them in a map of [UUID: Cancellable].

    So anyway, we remove them when publisher completes in the receiveCompletion. HOWEVER, it is possible for a publisher to IMMEDIATELY complete during the same tick. This causes the receiveCompletion code which removes the cancellable from the map to run BEFORE the cancellable is added to the map. The result is that these immediately-completing cancellables leak, in the sense that they build up and are never removed. Cancellables are very tiny, so we didn't notice the impact on memory, but it is bad hygiene.

    opened by gordonbrander 0
  • Allow specifying transaction for Update

    Allow specifying transaction for Update

    Allow updates to specify transaction under which state update should take place.

    This allows update functions to drive explicit animations. Animations, as well as state changes, can be specified.

    opened by gordonbrander 0
  • receiveValue with weak self closure

    receiveValue with weak self closure

    This allows store to be released before effect is complete, without creating a retain cycle. Ordinarily store's lifetime is the lifetime of the app, so this should not be an issue for single-store apps, but a weak reference to self should used here regardless. Additionally, this will now avoid a possible retain cycle if using multiple short-lived stores, such as one per component.

    opened by gordonbrander 0
  • Introduce KeyedCursorProtocol, remove ViewStore in favor of forward

    Introduce KeyedCursorProtocol, remove ViewStore in favor of forward

    This PR sketches out one potential solution to #18. It refactors our approach to sub-components by decomplecting action sending from state getting.

    • Removes ViewStore
    • Introduces Address.forward(send:tag:) which gives us an easy way to create tagged send functions. This solves one part of what ViewStore was solving.
    • Introduces Binding(state:send:tag:) which gives us the binding equivalent to Address.forward
    • Introduces KeyedCursorProtocol which offers an alternative cursor for subcomponents that need to be looked up within dynamic lists.

    This refactor is in response to the awkwardness of the ViewStore/Cursor paradigm for components that are part of a dynamic list. Even if we had created a keyed cursor initializer for ViewStore, it necessarily would have had to hold an optional (nillable) state. This is because ViewStore lookup was dynamic, and this trips up the lifetime typechecking around the model. In practice, a view would not exist if its model did not exist, but this is not a typesafe guarantee for dynamic list lookups.

    Anyway, the whole paradigm of looking up child from parent dynamically is a bit odd for list items. In SwiftUI the typical approach is to ForEach, and then pass the model data down as a static property to the view. This guarantees type safety, since a view holds its own copy of the data. What if we could do something more like that?

    The approach in this PR leans into this approach. State can be passed to sub-components as plain old properties. Address.forward can be used to create view-local send functions that you can pass down to sub-views. Binding gets a similar form. In both cases, we can use a closure to capture additional parent-scoped state, such as an ID for lookup within the parent model.

    Cursor sticks around, but mostly as a convenient way to create update functions for sub-components. We also introduce KeyedCursorProtocol which offers a keyed equivalent for dynamic lookup.

    Usage

    Sub-components become more "vanilla", just using bare properties and closures.

    struct ParentView: View {
        @StateObject = Store(
            AppModel(),
            AppEnvironment()
        )
    
        var body: some View {
            ChildModel(
                state: store.state.child,
                send: Address.forward(
                    send: store.send,
                    tag: ParentChildCursor.tag
                )
            )
        }
    }
    
    struct ChildView: View {
        var state: ChildModel
        var send: (ChildAction) -> Void
    
        var body: some View {
            Button(state.text) {
                send(.activate)
            }
        }
    }
    

    Prior art

    This approach is inspired by Reflex:

    • Forward https://github.com/mozilla/reflex/blob/c5e75e98bc601e2315b6d43e5e347263cf67359e/src/signal.js#L5
    • Cursor https://github.com/browserhtml/browserhtml/blob/master/src/Common/Cursor.js
    opened by gordonbrander 0
  • Cursors should support list items

    Cursors should support list items

    Cursors leverage static funcs for getting/setting right now, meaning they cannot support dynamic lookup. One place where you want dynamic lookup is in creating a cursor for an item in a dynamic list.

    Possible approaches

    Introduce cursor protocol that uses instance methods

    public protocol CursorProtocol {
        associatedtype Model: ModelProtocol
        associatedtype ViewModel: ModelProtocol
    
        /// Get an inner state from an outer state
        func get(state: Model) -> ViewModel
    
        /// Set an inner state on an outer state, returning an outer state
        func set(state: Model, inner: ViewModel) -> Model
    
        /// Tag an inner action, transforming it into an outer action
        func tag(_ action: ViewModel.Action) -> Model.Action
    }
    
    extension CursorProtocol {
        /// Update an outer state through a cursor.
        /// CursorProtocol.update offers a convenient way to call child
        /// update functions from the parent domain, and get parent-domain
        /// states and actions back from it.
        ///
        /// - `state` the outer state
        /// - `action` the inner action
        /// - `environment` the environment for the update function
        /// - Returns a new outer state
        public func update(
            state: Model,
            action viewAction: ViewModel.Action,
            environment: ViewModel.Environment
        ) -> Update<Model> {
            let next = ViewModel.update(
                state: self.get(state: state),
                action: viewAction,
                environment: environment
            )
            return Update(
                state: self.set(state: state, inner: next.state),
                fx: next.fx.map(self.tag).eraseToAnyPublisher(),
                transaction: next.transaction
            )
        }
    }
    

    Nit: for cursors with effectively static lifetimes, if you don't want to create a cursor for a child component every render, you need to save it somewhere. I think this is unavoidable if you want dynamic lookup.

    Introduce a concrete cursor type that leverages closures

    ...like Binding:

    public struct Cursor: CursorProtocol {
        private let _get: (Model) -> ViewModel
        private let _set: (Model, ViewModel) -> Model
        private let _tag: (ViewModel.Action) -> Model.Action
    
        /// Initialize a ViewStore using a get and send closure.
        public init(
            get: @escaping (Model) -> ViewModel,
            set: @escaping (Model, ViewModel) -> Model,
            tag: @escaping (ViewModel.Action) -> Model.Action
        ) {
            self._get = get
            self._set = set
            self._tag = tag
        }
    
        public func get(state: Model) {
            self._get(state)
        }
    
        public func set(state: Model, inner: ViewModel) {
            self._set(state, inner)
        }
    
        public func tag(_ action: ViewModel.Action) {
            self._tag(action)
        }
    }
    

    This gives you a simple syntax for one-offs via closure. The protocol allows us to hand-author optimized cursors.

    Introduce a way to wrap models with indexing information

    It may be that we can avoid instancing cursors if actions/models can be wrapped in indexing information. To investigate.

    ... I think this would complect parent and child domains.

    Introduce KeyedCursorProtocol

    A new static cursor protocol that allows for failable gets and keyed tagging fn. Details TBD.

    enhancement 
    opened by gordonbrander 0
  • Xcode14

    Xcode14 "Publishing changes from within view updates is not allowed, this will cause undefined behavior."

    New "purple warning" in Xcode 14 "Publishing changes from within view updates is not allowed, this will cause undefined behavior.", seemingly related to https://github.com/subconsciousnetwork/ObservableStore/blob/main/Sources/ObservableStore/ObservableStore.swift#L229

    self.state = next.state
    

    We must be tripping over some kind of footgun because we're not doing anything too fancy here.

    Leads:

    • https://developer.apple.com/forums/thread/711899
    • https://www.donnywals.com/xcode-14-publishing-changes-from-within-view-updates-is-not-allowed-this-will-cause-undefined-behavior/
    • https://stackoverflow.com/questions/73323869/swiftui-publishing-an-environment-change-from-within-view-update
    • https://www.hackingwithswift.com/forums/swiftui/xcode-14-publishing-changes-from-within-view-updates-is-not-allowed-this-will-cause-undefined-behavior/16434
    apple-bug 
    opened by gordonbrander 2
Releases(0.2.0)
  • 0.2.0(Sep 16, 2022)

    Version 0.2.0 is an important update to ObservableStore. It introduces the ability to create scoped stores for sub-components, called ViewStores. ViewStores are conceptually like bindings to stores, except that they expose the store API, instead of a binding API. This approach is inspired by the component mapping pattern from Elm.

    Changes

    • Introduce StoreProtocol, a protocol that describes a kind of store.
    • Introduce ViewStore<Model: ModelProtocol>, which implements StoreProtocol
    • Implement StoreProtocol for Store
    • Introduce CursorProtocol, which describes how to map from one domain to another, and provides a convenience function for updating child components.
    • Introduce ModelProtocol, which implements an update(state:action:environment) static function.
      • ModelProtocol allows us to treat action and environment as associatedtypes, which simplifies many of our other type signatures.
    • Synthesize update(state:actions:environment) method for any type that implements ModelProtocol. This allows you to dispatch multiple actions immediately in sequence, effectively composing multiple actions.
      • This helper replaces pipe
      • Useful when dispatching actions down to multiple child sub-components
    • Add new tests

    Breaking changes

    • Store requires that state implement ModelProtocol. This allows us to simplify the signatures of many other APIs
      • Update type signature changes from Update<State, Action> to Update<Model: ModelProtocol>.
      • Store type signature changes from Store<State, Action, Environment> to Store<Model: ModelProtocol>
      • Store initializer changes from Store.init(update:state:environment:) to Store.init(state:environment:). Now that state conforms to ModelProtocol, you don't have to explicitly pass in the update function as a closure. We can just call the protocol implementation.
    • store.binding has been removed in favor of Binding(store:get:send:)
    • Remove Update.pipe. Redundant now. Was never happy with it anyway. It was an inelegant way to accomplish the same thing as update(state:actions:environment:).
    • Join fx on main with a .default QoS. We have reduced the QoS from .userInteractive to avoid spamming that QoS with actions. This should not affect ordinary use of ObservableStore. fx are async/never block user interaction anyway, so a default QoS should be fine.
    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Mar 26, 2022)

    Introduces new features, bugfixes, and cleans up geneneric type arguments, function signatures, and argument labels.

    Note for upgrading from 0.0.7 to 0.1.0: 0.1.0 includes breaking changes to APIs. Because this version is pre-1.0, and we are still working out APIs, these breaking changes are expressed as a minor revision (0.1.0) instead of a major revision.

    Release notes:

    • Changed Store generic signature to Store<State, Action, Environment>. See https://github.com/gordonbrander/ObservableStore/pull/14
    • Changed update function signature to update(State, Action, Environment). See https://github.com/gordonbrander/ObservableStore/pull/14
    • Removed argument label from send so it is just Store.send(_). See https://github.com/gordonbrander/ObservableStore/pull/10
    • Changed argument label for subscribe to Store.subscribe(to:). See https://github.com/gordonbrander/ObservableStore/pull/10
    • Introduced .binding(get:tag:) so that constructing a binding without specifying an animation will leave transaction animation untouched. See https://github.com/gordonbrander/ObservableStore/pull/12
    • Added Update.mergeFx(_) which can be used to merge additional fx into an Update. Useful when composing update functions with Update.pipe. See https://github.com/gordonbrander/ObservableStore/pull/9
    • Removed Update.transaction method. See https://github.com/gordonbrander/ObservableStore/pull/9
    Source code(tar.gz)
    Source code(zip)
Owner
Subconscious
Tarot for Thought
Subconscious
🤖 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
🌾 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
Store-App - Store app made for IOS using Swift programming language

Store-App Store app views products, cart, and using login from https://fakestore

Anas Khalil 2 Jan 1, 2022
An experimental time traveling state store for SwiftUI

SwiftUI Time Travel A SwiftUI state store and view that allow you to scrub through an application's state. This is a super rough prototype: it's only

Tim Donnelly 139 Sep 14, 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
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
Lightweight observations and bindings in Swift

What is Hanson? Hanson is a simple, lightweight library to observe and bind values in Swift. It's been developed to support the MVVM architecture in o

Blendle 526 Oct 18, 2022
RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.

About Architecture concerns RxReduce Installation The key principles How to use RxReduce Tools and dependencies Travis CI Frameworks Platform Licence

RxSwift Community 125 Jan 29, 2022
📬 A lightweight implementation of an observable sequence that you can subscribe to.

Features Lightweight Observable is a simple implementation of an observable sequence that you can subscribe to. The framework is designed to be minima

Felix M. 133 Aug 17, 2022
Simple, lightweight swift bindings

Bindy Just a simple bindings. Installation Add pod 'Bindy' to your podfile, and run pod install SPM is supported too. Usage For now, Bindy has a coupl

Maxim Kotliar 25 Dec 12, 2022
A lightweight, event-driven architectural framework

Trellis Trellis features a declarative DSL that simplifies service bootstrapping: let cluster = try await Bootstrap { Group { Store(model:

Valentin Radu 25 Aug 16, 2022
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
🟣 Verge is a very tunable state-management engine on iOS App (UIKit / SwiftUI) and built-in ORM.

Verge.swift ?? An effective state management architecture for iOS - UIKit and also SwiftUI ?? _ An easier way to get unidirectional data flow _ _ Supp

VergeGroup 478 Dec 29, 2022
A powerful, minimal and composable architecture for building reactive iOS apps with SwiftUI or UIKit

SourceArchitecture A simple yet powerful framework for reactive programming with only a minimal optimized set of types. Sources are self-contained, hi

Daniel Hall 6 Nov 1, 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
Netflix Onboarding made with SwiftUI

OnBoardSwiftUI-Netflix Netflix Onboarding made with SwiftUI.

Shreyas Bhike 22 Dec 21, 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
Eazy is the missing piece in your SwiftUI and UIKit application.

Eazy is the missing piece in your SwiftUI and UIKit application. It aims at harmonizing how your views communicate with the model and vice versa in a clear and consistent way. Eazy can be used on any Apple platform.

Johan Thorell 7 Sep 18, 2022
Elm-parcel-capacitor - A sample setup to build an app with Elm, Capacitor, Parcel and Tailwind CSS

Elm, Capacitor, Parcel and Tailwindcss This project is a sample setup to build a

Anthonny Quérouil 10 May 9, 2022
A simple Elm-like Store for SwiftUI, based on ObservableObject

ObservableStore A simple Elm-like Store for SwiftUI, based on ObservableObject.

Gordon Brander 28 Nov 8, 2022