Unidirectional, transactional, operation-based Store implementation.

Overview

Swift Build Status Cov Platform Dispatch

Unidirectional, transactional, operation-based Store implementation for Swift and SwiftUI

Overview

Store eschews MVC in favour of a unidirectional data flow. When a user interacts with a view, the view propagates an action to a store that hold the application's data and business logic, which updates all of the views that are affected.

This works especially well with SwiftUI's declarative programming style, which allows the store to send updates without specifying how to transition views between states.

  • Stores: Holds the state of your application. You can have multiple stores for multiple domains of your app.
  • Actions: You can only perform state changes through actions. Actions are small pieces of data (typically enums or structs) that describe a state change. By drastically limiting the way state can be mutated, your app becomes easier to understand and it gets easier to work with many collaborators.
  • Views: A simple function of your state. This works especially well with SwiftUI's declarative programming style.

Getting started

TL;DR

Yet another counter:

import SwiftUI
import Store

struct CounterModel { var count: Int }

struct CounterView: View {
  @ObservedObject var store = Store(model: CounterModel(count: 0))
  
  var body: some View {
    VStack {
      Text(String(describing: store.binding.count))
      HStack {
        Button("Increase") { store.binding.count += 1 }
        Button("Decrease") { store.binding.count -= 1 }
      }
    }
  }
}

Yet another todo list:

final class Todo: Codable, Identifiable {
  let id = PushID.default.make()
  var text: String = "Untitled Todo"
  var done: Bool = false
}

final class TodoList: Codable {
  var todos: [Todo] = []
}

extension Store where M == TodoList {
  func addNewTodo() {
    mutate { $0.todos.append(Todo()) }
  }
  func move(from source: IndexSet, to destination: Int) {
    mutate { $0.todos.move(fromOffsets: source, toOffset: destination) }
  }
  func remove(todo: Todo) {
    mutate { model in 
      model.todos = model.todos.filter { $0.id != todo.id } 
    }
  }
}

// MARK: - Views

struct TodoView: View {
  @ObservedObject var store: CodableStore<Todo>
  let onRemove: () -> Void
  
  var body: some View {
    HStack {
      // Move handle.
      Image(systemName: "line.horizontal.3")
      // Text.
      if store.readOnlyModel.done {
        Text(store.readOnlyModel.text).strikethrough()
      } else {
        TextField("Todo", text: $store.binding.text)
      }
      Spacer()
      // Mark item as done.
      Toggle("", isOn: $store.binding.done)
      // Remove task.
      Button("Remove", action: onRemove)
    }.padding()
  }
}

struct TodoListView: View {
  @ObservedObject var store = CodableStore(model: TodoList())
  
  var body: some View {
    List {
      // Todo items.
      ForEach(store.readOnlyModel.todos) { todo in
        TodoView(store: CodableStore(model: todo)) { store.remove(todo: todo) }
      }
      .onMove(perform: store.move)
      // Add a new item.
      Button("New Todo", action: store.addNewTodo)
    }
  }
}

Store

Stores contain the application state and logic. Their role is somewhat similar to a model in a traditional MVC, but they manage the state of many objects — they do not represent a single record of data like ORM models do. More than simply managing a collection of ORM-style objects, stores manage the application state for a particular domain within the application.

This allows an action to result in an update to the state of the store. After the stores are updated, they notify the observers that their state has changed, so the views may query the new state and update themselves.

struct Counter { var count = 0 }
let store = Store<Counter>(model: Counter())

Actions

An action represent an operation on the store that is cancellable and is performed transactionally on the Store.

Actions can be defined using an enum or a struct.

enum CounterAction: Action {
  case increase
  case decrease

  func mutate(context: TransactionContext<Store<Counter>, Self>) {
    defer {
      // Remember to always call `fulfill` to signal the completion of this operation.
      context.fulfill()
    }
    switch self {
    case .increase(let amount):
      context.update { $0.count += 1 }
    case .decrease(let amount):
      context.update { $0.count -= 1 }
    }
  }
  
  func cancel(context: TransactionContext<Store<Counter>, Self>) { }
}
struct IncreaseAction: Action {
  let count: Int

  func mutate(context: TransactionContext<Store<Counter>, Self>) {
    defer {
      // Remember to always call `fulfill` to signal the completion of this operation.
      context.fulfill()
    }
    context.mutate { $0.count += 1 }
  }
  
  func cancel(context: TransactionContext<Store<Counter>, Self>) { }
}

Alternatively using a binding to change the store value (through store.binding) or calling store.mutate { model in } would implicitly create and run an action that is performed synchronously.

Button("Increase") { store.binding.count += 1 }

Documentation

class Store<M>: ObservableObject, Identifiable

This class is the default implementation of the MutableStore protocol. A store wraps a value-type model, synchronizes its mutations, and emits notifications to its observers any time the model changes.

Model mutations are performed through Actions: These are operation-based, cancellable and abstract the concurrency execution mode. Every invokation of run(action:) spawns a new transaction object that can be logged, rolled-back and used to inspect the model diffs (see TransactionDiff).

It's recommendable not to define a custom subclass (you can use CodableStore if you want diffing and store serialization capabilities). Domain-specific functions can be added to this class by writing an extension that targets the user-defined model type. e.g.

 let store = Store(model: Todo())
 [...]
 extension Store where M == Todo {
   func upload() -> Future<Void, Error> {
     run(action: TodoAction.uploadAndSynchronizeTodo, throttle: 1)
   }
 }

Model

  • let modelStorage: M The associated storage for the model (typically a value type).

  • let binding: BindingProxy<M> Read-write access to the model through @Binding in SwiftUI. e.g. Toggle("...", isOn: $store.binding.someProperty). When the binding set a new value an implicit action is being triggered and the property is updated.

Observation

  • func notifyObservers() Notify the store observers for the change of this store. Store and CodableStore are ObservableObjects and they automatically call this function (that triggers a objectWillChange publlisher) every time the model changes. Note: Observers are always scheduled on the main run loop.

  • func performWithoutNotifyingObservers(_ perform: () -> Void) The block passed as argument does not trigger any notification for the Store observers. e.g. By calling reduceModel(transaction:closure:) inside the perform block the store won't pubblish any update.

Combine Stores

  • func makeChildStore<C>(keyPath: WritableKeyPath<M, C>) -> Store<C> Used to express a parent-child relationship between two stores. This is the case when it is desired to have a store (child) to manage to a subtree of the store (parent) model. CombineStore define a merge strategy to reconcile back the changes from the child to the parent. e.g.
struct Model { let items: [Item] }
let store = Store(model: Model())
let child = store.makeChildStore(keyPath: \.[0])

Transactions

  • func transaction<A: Action, M>( action: A, mode: Executor.Strategy = default) -> Transaction<A> Builds a transaction object for the action passed as argument. This can be executed by calling the run function on it. Transactions can depend on each other's completion by calling the depend(on:) function. e.g.
let t1 = store.transaction(.addItem(cost: 125))
let t2 = store.transaction(.checkout)
let t3 = store.transaction(.showOrdern)
t2.depend(on: [t1])
t3.depend(on: [t2])
[t1, t2, t3].run()

Running Actions

  • func run<A: Action, M>(action: A, mode: Executor.Strategy = default, throttle: TimeInterval = default) -> Future<Void, Error> Runs the action passed as argument on this store and returns a future that is resolved when the action execution has completed.

  • func run<A: Action, M>(actions: [A], mode: Executor.Strategy = default) -> Future<Void, Error> Runs all of the actions passed as argument sequentially. This means that actions[1] will run after actions[0] has completed its execution, actions[2] after actions[1] and so on.

Middleware

  • func register(middleware: Middleware) Register a new middleware service. Middleware objects are notified whenever a transaction running in this store changes its state.

  • func unregister(middleware: Middleware) Unregister a middleware service.

class CodableStore<M: Codable>: Store<M>

A Store subclass with serialization capabilities. Additionally a CodableStore can emits diffs for every transaction execution (see the lastTransactionDiff pubblisher). This can be useful for store synchronization (e.g. with a local or remote database).

  • static func encode<V: Encodable>(model: V) -> EncodedDictionary Encodes the model into a dictionary.

  • static func encodeFlat<V: Encodable>(model: V) -> FlatEncoding.Dictionary Encodes the model into a flat dictionary. The resulting dictionary won't be nested and all of the keys will be paths. e.g. {user: {name: "John", lastname: "Appleseed"}, tokens: ["foo", "bar"] turns into

{
   user/name: "John",
   user/lastname: "Appleseed",
   tokens/0: "foo",
   tokens/1: "bar"
 } 

This is particularly useful to synchronize the model with document-based databases (e.g. Firebase).

Demos

hacker_news_demo

Cookbook

A collection of Store usage scenarios.

Serialization and Diffing

TL;DR

struct MySerializableModel: Codable {
var count = 0
var label = "Foo"
var nullableLabel: String? = "Bar"
var nested = Nested()
var array: [Nested] = [Nested(), Nested()]
  struct Nested: Codable {
  var label = "Nested struct"
  }
}

let store = SerializableStore(model: TestModel(), diffing: .async)
store.$lastTransactionDiff.sink { diff in
  // diff is a `TransactionDiff` obj containing all of the changes that the last transaction has applied to the store's model.
}

A quick look at the TransactionDiff interface:

public struct TransactionDiff {
  /// The set of (`path`, `value`) that has been **added**/**removed**/**changed**.
  ///
  /// e.g. ``` {
  ///   user/name: <added ⇒ "John">,
  ///   user/lastname: <removed>,
  ///   tokens/1:  <changed ⇒ "Bar">,
  /// } ```
  public let diffs: [FlatEncoding.KeyPath: PropertyDiff]
  /// The identifier of the transaction that caused this change.
  public let transactionId: String
  /// The action that caused this change.
  public let actionId: String
  /// Reference to the transaction that cause this change.
  public var transaction: AnyTransaction
  /// Returns the `diffs` map encoded as **JSON** data.
  public var json: Data
}

/// Represent a property change.
/// A change can be an **addition**, a **removal** or a **value change**.
public enum PropertyDiff {
  case added(new: Codable?)
  case changed(old: Codable?, new: Codable?)
  case removed
}

Diff output:

▩ INFO (-LnpwxkPuE3t1YNCPjjD) UPDATE_LABEL [0.045134 ms]
▩ DIFF (-LnpwxkPuE3t1YNCPjjD) UPDATE_LABEL {
    · label: <changed ⇒ (old: Foo, new: Bar)>,
    · nested/label: <changed ⇒ (old: Nested struct, new: Bar)>,
    · nullableLabel: <removed>
  }

Combining Stores

As your app logic grows could be convient to split store into smaller one, still using the same root model. This can be achieved by using the makeChildStore(keyPath:) API.

struct App {
  struct Todo {
    var name: String = "Untitled"
    var description: String = "N/A"
    var done: Bool = false
  }
  var todos: [Todo] = []
}

// This action targets a Store<Todo>...
struct TodoActionMarkAsDone: Action {
  func reduce(context: TransactionContext<Store<App.Todo>, Self>) {
    defer { context.fulfill() }
    context.reduceModel { $0.done = true }
  }
}

// ..While this one the whole collection Store<[Todo]>
struct TodoListActionCreateNew: Action {
  let name: String
  let description: String
  func reduce(context: TransactionContext<Store<Array<App.Todo>>, Self>) {
    defer { context.fulfill() }
    let new = Root.Todo(name: name, description: description)
    context.reduceModel {
      $0.append(new)
    }
  }
}

let appModel = App()
let rootStore = Store(model: appModel)

let todoListStore = rootStore.makeChildStore(keyPath: \.todos)
todoListStore.run(action: TodoListActionCreateNew(name: "New", decription: "New"), mode: .sync)

let todoStore = rootStore.makeChildStore(keyPath: \.[0])
todoStore.run(action: TodoActionMarkAsDone(), mode: .sync)

This is a good strategy to prevent passing down the whole application store as a dependency when not needed (e.g. maybe your datasource just need the TodoList store and your cell the single-value Todo store).

Advanced

Dispatch takes advantage of Operations and OperationQueues and you can define complex dependencies between the operations that are going to be run on your store.

Chaining actions

store.run(actions: [
  CounterAction.increase(amount: 1),
  CounterAction.increase(amount: 1),
  CounterAction.increase(amount: 1),
]) { context in
  // Will be executed after all of the transactions are completed.
}

Actions can also be executed in a synchronous fashion.

store.run(action: CounterAction.increase(amount: 1), strategy: .mainThread)
store.run(action: CounterAction.increase(amount: 1), strategy: .sync)

Complex Dependencies

You can form a dependency graph by manually constructing your transactions and use the depend(on:) method.

let t1 = store.transaction(.addItem(cost: 125))
let t2 = store.transaction(.checkout)
let t3 = store.transaction(.showOrdern)
t2.depend(on: [t1])
t3.depend(on: [t2])
[t1, t2, t3].run()

Throttling transactions

Transactions can express a throttle delay.

func calledOften() {
  store.run(.myAction, throttle: 0.5)
}

Tracking a transaction state

Sometimes it's useful to track the state of a transaction (it might be useful to update the UI state to reflect that).

store.run(action: CounterAction.increase(amount: 1)).$state.sink { state in
  switch(state) {
  case .pending: ...
  case .started: ...
  case .completed: ...
  }
}

Checking the diff state of a specific property after a transaction

sink = store.$lastTransactionDiff.sink { diff in
  diff.query { $0.path.to.my.property }.isChanged() // or .isRemoved(), .isAdded()
}

Dealing with errors

struct IncreaseAction: Action {
  let count: Int

  func reduce(context: TransactionContext<Store<Counter>, Self>) {
    // Remember to always call `fulfill` to signal the completion of this operation.
    defer { context.fulfill() }
    // The operation terminates here because an error has been raised in this dispatch group.
    guard !context.rejectOnPreviousError() { else return }
    // Kill the transaction and set TransactionGroupError.lastError.
    guard store.model.count != 42 { context.reject(error: Error("Max count reach") }
    // Business as usual...
    context.reduceModel { $0.count += 1 }
  }
}

Cancellation

let cancellable: AnyCancellable = store.run(action: CounterAction.increase(amount: 1)).eraseToAnyCancellable();
cancellable.cancel()
▩ 𝙄𝙉𝙁𝙊 (-Lo4riSWZ3m5v1AvhgOb) INCREASE [✖ canceled]
Comments
  • Installation Section

    Installation Section

    Hey, your library is really interesting.

    The only problem I found was the README.md, which lacks an Installation Section I created this iOS Open source Readme Template so you can take a look on how to easily create an Installation Section If you want, I can help you to organize the lib.

    What are your thoughts? 😄

    opened by lfarah 7
  • [Question] Async action with combine

    [Question] Async action with combine

    Thank you for writing this library, I'm new to this architecture and trying to make it works. However I'm struggling to implement an async task for reducer.

    For example: in Counter actions, make a GET call to remote server and then increase count with the response.

    Can you please show the proper way to do async / side effect operation?

    opened by nielstj 5
  • Compilation error in RecorderMiddleware.swift

    Compilation error in RecorderMiddleware.swift

    KeyCommands.register(input: "n", modifierFlags: .command) { [weak self] in

    => one of those method calls gives the following error:

    type 'KeyCommands' has no member 'register'

    opened by majorgilles 5
  • iOS 14 compatibility issues

    iOS 14 compatibility issues

    Hi! I was trying to get the getting-started code snippets working in a freshly created swiftUI project that targets iOS 14. But I am running into issues.

    First, perhaps unrelated, I had to bump the iOS version in Package.swift from v12 to v13 in a fork, to able to resolve it inside XCode as a SwiftPackage, because of features like Future requiring at least iOS v13.

    After getting the package installed I tried out the snippet in my ContentView, but got stuck at following errors: Screenshot 2021-02-03 at 12 19 40

    opened by nylki 4
  • Combine separate reducers

    Combine separate reducers

    I'm looking to reproduce a common redux pattern, combineReducers when you split your slices (types/actions/reducers) in separate files and combine them in your root store.

    I have a Counter.swift:

    import Store
    
    struct CounterStore: SerializableModelProtocol {
        var count = 0
    }
    
    enum CounterAction: ActionProtocol {
    
        case increase(amount: Int)
        case decrease(amount: Int)
    
        var id: String {
            switch self {
            case .increase(_): return "INCREASE"
            case .decrease(_): return "DECREASE"
            }
        }
    
        func reduce(context: TransactionContext<Store<Counter>, Self>) {
            defer {
                context.fulfill()
            }
            switch self {
            case .increase(let amount):
                context.reduceModel { $0.count += amount }
            case .decrease(let amount):
                context.reduceModel { $0.count -= amount }
            }
        }
    }
    

    And a root Store.swift

    import Store
    
    struct RootStore: SerializableModelProtocol {
        var counter = CounterStore();
    }
    
    let store = SerializableStore<RootStore>(model: RootStore())
    

    However this does not work as the the action reduce function does not receive the proper context (.counter key from RootStore).

    Do you think such pattern is possible? Do you have any examples of a working setup?

    Thanks for the lib!

    opened by mgcrea 4
  • Expose Transaction.init(action:store:)

    Expose Transaction.init(action:store:)

    This is currently private, and causes errors: initializer is inaccessible due to 'internal' protection level.

    Exposing it brings the code up to date with the documentation in the Readme.

    opened by danpalmer 4
  • Is it okay to use a class instead of a struct for a store?

    Is it okay to use a class instead of a struct for a store?

    I am really struggling with this:

    struct RequestLocation: ActionType {
      let location: CLLocation
    
      func reduce(context: TransactionContext<Store<LocationStore>, Self>) {
        defer {
          // Remember to always call `fulfill` to signal the completion of this operation.
          context.fulfill()
        }
        context.reduceModel { store -> (Void) in
          CoreLocationProxy().locationPublisher().map { location in
            store.location = location
          }
        }
      }
    }
    

    Compile error: Escaping closure captures 'inout' parameter 'store'

        context.reduceModel { store -> (Void) in
          store.requestLocationCancellable = store.proxy.locationPublisher().assign(to: \LocationStore.location, on: store)
        }
    

    Compile error: Cannot convert value of type 'WritableKeyPath<LocationStore, CLLocation>' to expected argument type 'ReferenceWritableKeyPath<_, CLLocation>'

    I don't see how to make this work without turning my store into a class. It works then. Would that code any issue?

    Some more examples using publishers, etc would be quite useful btw.

    opened by mycroftcanner 3
  • type 'CounterAction' does not conform to protocol 'ActionType'

    type 'CounterAction' does not conform to protocol 'ActionType'

    Type 'CounterAction' does not conform to protocol 'ActionType'

    I tried typealias AssociatedStoreType = Counter and Store but it doesn't seem to work. I'll take a nap and try again later.

    opened by mycroftcanner 2
  • Handling child stores for lists of data

    Handling child stores for lists of data

    I am thinking about using stores for new app I will be working on, but I cannot imagine how would the stores handle showing detail of a movie from the list of movies.

    Imagine if I have app that loads list of items and I select on of the items to see its detail information. I am on the screen, but meanwhile the app updates the list in the background and the corresponding index does not match the same movie now, what will happen, how is this handled/should be handled

    I’ve seen some other examples where there was selectedItem in the store/model, but that seems sort of too simple or am I missing something?

    opened by zdnk 1
  • Type 'CounterAction' does not conform to protocol 'ActionProtocol'

    Type 'CounterAction' does not conform to protocol 'ActionProtocol'

    Just copy pasted the TLDR in the README and I'm encountering this error:

    Type 'CounterAction' does not conform to protocol 'ActionProtocol'
    

    Looks similar to #13 but there is no answer there.

    I'm a Swift noob so it's probably obvious, looks like it requires a typealias AssociatedStoreType property but not sure what I should put there.

    Thanks!


    EDIT, looks like the README is not up to date with the protocol.

    opened by mgcrea 1
  • Potential race condition in Store.run (?)

    Potential race condition in Store.run (?)

    In the code snippet below, Store's run method invokes transactionObj.run and then shortly (probably fast enough for it to work) invokes transactionObj.throttle. Would it not be better to reverse the order of run and throttle?

    @discardableResult @inlinable @inline(__always)
    public func run<A: ActionProtocol, M>(
        action: A,
        mode: Dispatcher.Strategy = .async(nil),
        throttle: TimeInterval = 0,
        handler: Dispatcher.TransactionCompletionHandler = nil
    ) -> Transaction<A> where A.AssociatedStoreType: Store<M> {
      let transactionObj = transaction(action: action, mode: mode)
      transactionObj.run(handler: handler) // RUN STARTED HERE
      if throttle > TimeInterval.ulpOfOne {
        transactionObj.throttle(throttle) // THROTTLING APPLIED HERE
      }
      return transactionObj
    }
    

    That is, instead of:

      let transactionObj = transaction(action: action, mode: mode)
      transactionObj.run(handler: handler) // RUN STARTED HERE
      if throttle > TimeInterval.ulpOfOne {
        transactionObj.throttle(throttle) // THROTTLING APPLIED HERE
      }
    

    use:

      let transactionObj = transaction(action: action, mode: mode)
    
      if throttle > TimeInterval.ulpOfOne {
        transactionObj.throttle(throttle) // THROTTLING APPLIED HERE
      }
      transactionObj.run(handler: handler) // RUN STARTED HERE
    

    ?

    opened by stuartro 1
Releases(3.8.0)
Owner
Alex Usbergo
Technical Lead | Sr Software Engineer at Google. Ex-Microsoft/Ex-Skype.
Alex Usbergo
Arena is an implementation of the generational arena data structure in Swift.

Arena This package is very much work in progress. Arena is an implementation of the generational arena data structure in Swift. An Arena is useful for

null 7 Dec 7, 2021
TemplateKit - React inspired framework for building component-based user interfaces in Swift

TemplateKit React-inspired framework for building component-based user interface

null 0 Feb 6, 2022
🔥 🔥 🔥Support for ORM operation,Customize the PQL syntax for quick queries,Support dynamic query,Secure thread protection mechanism,Support native operation,Support for XML configuration operations,Support compression, backup, porting MySQL, SQL Server operation,Support transaction operations.

?? ?? ??Support for ORM operation,Customize the PQL syntax for quick queries,Support dynamic query,Secure thread protection mechanism,Support native operation,Support for XML configuration operations,Support compression, backup, porting MySQL, SQL Server operation,Support transaction operations.

null 60 Dec 12, 2022
UDF (Unidirectional Data Flow) is a library based on Unidirectional Data Flow pattern.

UDF (Unidirectional Data Flow) is a library based on Unidirectional Data Flow pattern. It lets you build maintainable, testable, and scalable apps.

inDriver 51 Dec 23, 2022
A simple, reliable and scalable delivery API for transactional push notifications for websites and applications

Catapush is a simple, reliable and scalable delivery API for transactional push notifications for websites and applications. Ideal for sending data-dr

Catapush 0 Dec 29, 2021
ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift.

ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift. ReSwift helps you to separate three important concerns of your app's components.

null 7.3k Jan 9, 2023
JSONHelper - ✌ Convert anything into anything in one operation; JSON data into class instances, hex strings into UIColor/NSColor, y/n strings to booleans, arrays and dictionaries of these; anything you can make sense of!

JSONHelper Convert anything into anything in one operation; hex strings into UIColor/NSColor, JSON strings into class instances, y/n strings to boolea

Baris Sencan 788 Jul 19, 2022
Operation Oriented Programming in Swift

Flow Flow is a lightweight Swift library for doing operation oriented programming. It enables you to easily define your own, atomic operations, and al

John Sundell 218 Feb 6, 2022
ClassicPhotos is a TableView App demos how to optimize image download and filter with operation queue.

ClassicPhotos ClassicPhotos is a TableView App demos how to optimize image download and filter with operation queue. With Operation and Operation Queu

Kushal Shingote 2 Dec 18, 2021
A ReactNative module for icloud operation

react-native-cloud-store A ReactNative module for cloud operation (currently only support icloud dirve) Developing, use at your risk, issue and PR is

xiao xin 24 Dec 22, 2022
iOS library for device fingerprinting. Does not require server APIs to work, fully client-side operation.

Lightweight iOS library for local device fingerprinting Installation (CocoaPods) # Podfile pod 'FingerprintJS' Note: If you've never used CocoaPods fo

FingerprintJS 45 Dec 17, 2022
🍎 An App to check whether a non-App Store app is in App Store.

AppStorify ?? An App to check whether a non-App Store app is in App Store. Benfits Use App Store's upgrade mechanism instead of app's. App Store apps

seedgou 58 Dec 7, 2022
daemon DICOMweb store proxying to pacs in c-store protocol

POSTCSTORE versionado versión autor comentario 2021-11-02 Jacques Fauquex versión inicial 2021-11-02 Jacques Fauquex aetLocal en la ruta del servicio.

null 1 Mar 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
A library for reactive and unidirectional Swift applications

ReactorKit is a framework for a reactive and unidirectional Swift application architecture. This repository introduces the basic concept of ReactorKit

ReactorKit 2.5k Dec 28, 2022
VueFlux is the architecture to manage state with unidirectional data flow 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
UDF (Unidirectional Data Flow) architecture on SwiftUI/Combine

The license The SwiftUI-UDF stays under a dual license (email confirmation required): It can be Free for non-commercial use, public repository or star

Max Kuznetsov 13 Nov 10, 2022
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 Dec 25, 2022
🔄 Unidirectional data flow in Swift.

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 o

Reactor 175 Jul 9, 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