Protocols

The following protocols are available globally.

  • MiddlewareProtocol is a plugin, or a composition of several plugins, that are assigned to the app global StoreType pipeline in order to handle each action received (InputActionType), to execute side-effects in response, and eventually dispatch more actions (OutputActionType) in the process. It can also access the most up-to-date StateType while handling an incoming action.

    See more

    Declaration

    Swift

    public protocol MiddlewareProtocol
  • ActionHandler defines a protocol for entities able to handle actions - defined by the associated type ActionType.

    The only protocol requirement is a function that allows other entities to dispatch actions, so Views (or Presenters, ViewModels) in your UI layer, or even Middlewares can create actions of a certain type and send to your store, that is generalized by this protocol.

    See more

    Declaration

    Swift

    public protocol ActionHandler
  • StateProvider defines a protocol for entities able to offer state publishers (Combine Publisher, RxSwift Observable, ReactiveSwift SignalProducer) of certain StateType, so everybody can observe the global state changes through this container. Usually a Store will implement that, but it can also be a StoreProjection with a state that is derived from the global source-of-truth.

    The only protocol requirement is to offer a property statePublisher that will allow other entities to subscribe to state changes and react to those.

    See more

    Declaration

    Swift

    public protocol StateProvider
  • A protocol that defines the two expected roles of a “Store”: receive/distribute actions (ActionHandler); and publish changes of the the current app state (StateProvider) to possible subscribers. It can be a real store (such as ReduxStoreBase) or just a “proxy” that acts on behalf of a real store, for example, in the case of StoreProjection.

    See more

    Declaration

    Swift

    public protocol StoreType : ActionHandler, StateProvider
  • Undocumented

    See more

    Declaration

    Swift

    public protocol ReduxStoreProtocol : AnyObject, StoreType
  • A protocol to generalize MiddlewareReader. Unless you look for some very special behaviour, you should use MiddlewareReader directly which provides everything needed for your Middleware dependency injection.

    See more

    Declaration

    Swift

    public protocol MiddlewareReaderProtocol
  • Middleware is a plugin, or a composition of several plugins, that are assigned to the ReduxStoreProtocol pipeline in order to handle each action received (InputActionType), to execute side-effects in response, and eventually dispatch more actions (OutputActionType) in the process. This happens before the Reducer to do its job.

    We can think of a Middleware as an object that transforms actions into sync or async tasks and create more actions as these side-effects complete, also being able to check the current state at any point.

    An action is a lightweight structure, typically an enum, that is dispatched into the ActionHandler (usually a StoreProtocol). A Store like ReduxStoreProtocol enqueues a new action that arrives and submits it to a pipeline of middlewares. So, in other words, a Middleware is class that handles actions, and has the power to dispatch more actions to the ActionHandler chain. The Middleware can also simply ignore the action, or it can execute side-effects in response, such as logging into file or over the network, or execute http requests, for example. In case of those async tasks, when they complete the middleware can dispatch new actions containing a payload with the response (a JSON file, an array of movies, credentials, etc). Other middlewares will handle that, or maybe even the same middleware in a future RunLoop, or perhaps some Reducer, as reducers pipeline is at the end of every middleware pipeline.

    Middlewares can schedule a callback to be executed after the reducer pipeline is done mutating the global state. At that point, the middleware will have access to the new state, and in case it cached the old state it can compare them, log, audit, perform analytics tracking, telemetry or state sync with external devices, such as Apple Watches. Remote Debugging over the network is also a great use of a Middleware.

    Every action dispatched also comes with its action source, which is the primary dispatcher of that action. Middlewares can access the file, line, function and additional information about the entity responsible for creating and dispatching that action, which is a very powerful debugging information that can help developers to trace how the information flows through the app.

    Because the Middleware receive all actions and accesses the state of the app at any point, anything can be done from these small and reusable boxes. For example, the same CoreLocation middleware could be used from an iOS app, its extensions, the Apple Watch extension or even different apps, as long as they share some sub-state struct.

    Some suggestions of middlewares:

    • Run Timers, pooling some external resource or updating some local state at a constant time
    • Subscribe for CoreData, Realm, Firebase Realtime Database or equivalent database changes
    • Be a CoreLocation delegate, checking for significant location changes or beacon ranges and triggering actions to update the state
    • Be a HealthKit delegate to track activities, or even combining that with CoreLocation observation in order to track the activity route
    • Logger, Telemetry, Auditing, Analytics tracker, Crash report breadcrumbs
    • Monitoring or debugging tools, like external apps to monitor the state and actions remotely from a different device
    • WatchConnectivity sync, keep iOS and watchOS state in sync
    • API calls and other “cold observables”
    • Network Reachability
    • Navigation through the app (Redux Coordinator pattern)
    • CoreBluetooth central or peripheral manager
    • CoreNFC manager and delegate
    • NotificationCenter and other delegates
    • WebSocket, TCP Socket, Multipeer and many other connectivity protocols
    • RxSwift observables, ReactiveSwift signal producers, Combine publishers
    • Observation of traits changes, device rotation, language/locale, dark mode, dynamic fonts, background/foreground state
    • Any side-effect, I/O, networking, sensors, third-party libraries that you want to abstract
                      ┌─────┐                                                                                        ┌─────┐
                                handle   ┌──────────┐ request            response     ┌──────────┐ dispatch        
                              ┌─────────▶│Middleware├─────────────▶ External│─────────────▶│Middleware│───────────▶│Store│─   ...
                               Action    Pipeline  side-effects  World    side-effects  callback  New Action      
                                        └──────────┘                                 └──────────┘            └─────┘
    ┌──────┐ dispatch                         
    Button│─────────▶│Store│──▶│                └───afterReducer─────┐                   ┌────────┐
    └──────┘ Action                                                                ┌─▶│ View 1 
                                                                ┌─────┐               └────────┘
                               reduce   ┌──────────┐                  onNext        ┌────────┐
                              └─────────▶│ Reducer  ├───────────▶│Store│────────────▶├─▶│ View 2 
                                Action    Pipeline  New state        New state     └────────┘
                      └─────┘     +        └──────────┘            └─────┘               ┌────────┐
                                  State                                                └─▶│ View 3 
                                                                                          └────────┘
    

    Middleware protocol is generic over 3 associated types:

    InputActionType:

    The Action type that this Middleware knows how to handle, so the store will forward actions of this type to this middleware. Thanks to optics, this action can be a sub-action lifted to a global action type in order to compose with other middlewares acting on the global action of an app. Please check lift(inputActionMap:outputActionMap:stateMap:) for more details.

    OutputActionType:

    The Action type that this Middleware will eventually trigger back to the store in response of side-effects. This can be the same as InputActionType or different, in case you want to separate your enum in requests and responses. Thanks to optics, this action can be a sub-action lifted to a global action type in order to compose with other middlewares acting on the global action of an app. Please check lift(inputActionMap:outputActionMap:stateMap:) for more details.

    StateType:

    The State part that this Middleware needs to read in order to make decisions. This middleware will be able to read the most up-to-date StateType from the store at any point in time, but it can never write or make changes to it. In some cases, middleware don’t need reading the whole global state, so we can decide to allow only a sub-state, or maybe this middleware doesn’t need to read any state, so the StateTypecan safely be set to Void. Thanks to lenses, this state can be a sub-state lifted to a global state in order to compose with other middlewares acting on the global state of an app. Please check lift(inputActionMap:outputActionMap:stateMap:) for more details.

    When implementing your Middleware, all you have to do is to handle the incoming actions:

    When implementing your Middleware, all you have to do is to handle the incoming actions:

    class LoggerMiddleware: Middleware {
        typealias InputActionType = AppGlobalAction // It wants to receive all possible app actions
        typealias OutputActionType = Never          // No action is generated from this Middleware
        typealias StateType = AppGlobalState        // It wants to read the whole app state
    
        var getState: GetState<AppGlobalState>!
    
        func receiveContext(getState: @escaping GetState<AppGlobalState>, output: AnyActionHandler<Never>) {
            self.getState = getState
        }
    
        func handle(action: AppGlobalAction, from dispatcher: ActionSource, afterReducer: inout AfterReducer) {
            let stateBefore: AppGlobalState = getState()
            let dateBefore = Date()
    
            afterReducer = .do {
                let stateAfter = self.getState()
                let dateAfter = Date()
                let source = "\(dispatcher.file):\(dispatcher.line) - \(dispatcher.function) | \(dispatcher.info ?? "")"
    
                Logger.log(action: action, from: source, before: stateBefore, after: stateAfter, dateBefore: dateBefore, dateAfter: dateAfter)
            }
        }
    }
    
    class FavoritesAPIMiddleware: Middleware {
        typealias InputActionType = FavoritesAction  // It wants to receive only actions related to Favorites
        typealias OutputActionType = FavoritesAction // It wants to also dispatch actions related to Favorites
        typealias StateType = FavoritesModel         // It wants to read the app state that manages favorites
    
        var getState: GetState<FavoritesModel>!
        var output: AnyActionHandler<FavoritesAction>!
    
        func receiveContext(getState: @escaping GetState<FavoritesModel>, output: AnyActionHandler<FavoritesAction>) {
            self.getState = getState
            self.output = output
        }
    
        func handle(action: FavoritesAction, from dispatcher: ActionSource, afterReducer: inout AfterReducer) {
            guard let .toggleFavorite(movieId) = action else { return }
    
            let favoritesList = getState()
            let makeFavorite = !favoritesList.contains(where: { $0.id == movieId })
    
            API.changeFavorite(id: movieId, makeFavorite: makeFavorite) (completion: { result in
                switch result {
                case let .success(value):
                    self.output.dispatch(.changedFavorite(movieId, isFavorite: true), info: "API.changeFavorite callback")
                case let .failure(error):
                    self.output.dispatch(.changedFavoriteHasFailed(movieId, isFavorite: false, error: error), info: "API.changeFavorite callback")
                }
            })
        }
    }
    

    SwiftUI Side-Effects

    See more

    Declaration

    Swift

    @available(*, deprecated, message: "Use `MiddlewareProtocol` instead of `Middleware`. This protocol will be removed on 1.0.")
    public protocol Middleware : MiddlewareProtocol
  • Protocol for a semigroup, any algebraic structure that allows two of its elements to be combined into one, (A, A) -> A, for any of its elements and keeping associativity property for all the cases, for example: (a1 <> a2) <> a3 = a1 <> (a2 <> a3) for any as in A.

    Axioms:

    • Totality
    • Associativity

    For example, having a f(x) -> x and a g(x) -> x, one would be able to compose h = f <> g in a way that the new function h(x) will be similar to g(f(x))

    See more

    Declaration

    Swift

    public protocol Semigroup
  • Protocol for a monoid algebra, allowing monoidal composition. It’s a Semigroup with identity element, element which, when combined to any other element, will keep the other elemenet unchanged, regardless if the composition happened from the left or from the right, for example: a <> identity = identity <> a = a, for any a in A.

    Axioms:

    • Totality
    • Associativity
    • Identity

    For example, having a f(x) -> x and a g(x) -> x, one would be able to compose h = f <> g in a way that the new function h(x) will be similar to g(f(x)), and there should be a function i(x) where i, when composed to any other function, will not change the result: f <> i = i <> f = f, for f, g, h and all other endo-functions.

    See more

    Declaration

    Swift

    public protocol Monoid : Semigroup
  • Abstraction over subscription types from reactive frameworks. This abstraction uses concept similar to type-erasure or protocol witness pattern, wrapping the behaviour of concrete implementations and delegating to them once the wrapper funcions are called.

    See more

    Declaration

    Swift

    public protocol SubscriptionType
  • Abstraction over subscription collection (DisposeBag or Set<AnyCancellable depending on your chosen reactive framework), useful for keeping subscriptions alive while the parent class is alive, binding the lifecycle of subscriptions to the lifecycle of views, view controllers or presenters. Subscriptions added to a subscription collection will be cancelled/disposed automatically once the collection gets deallocated, stopping any pending operation and cleaning up the resources. This abstraction uses concept similar to type-erasure or protocol witness pattern, wrapping the behaviour of concrete implementations and delegating to them once the wrapper funcions are called.

    See more

    Declaration

    Swift

    public protocol SubscriptionCollection