Store
open class Store<S, D> : PartialStore<S> where S : State, D : SideEffectDependencyContainer
The Store is a sort of single entry point that handles the logic of your application.
In Katana, all the various pieces of information that your application manages should be stored
in a single atom, called state (see also State protocol).
The Store, however, doesn’t really implements any application specific logic: this class
only manages operations that are requested by the application-specific logic. In particular,
you can require the Store to execute something by dispatching a dispatchable item.
Currently the store handles 2 types of dispatchable: State Updater, Side Effect
Update the state
As written before, in Katana every relevant information in the application should be stored in the
Store’s state. The only way to update the state is to dispatch a StateUpdater.
At this point the Store will execute the following operations:
- it executes the interceptors
- it creates the new state, by invoking the
updateStatefunction of theStateUpdater - it updates the state
- it resolves the promise
Handle to Business Logic
Non trivial applications require to interact with external services and/or implement complex logics.
The proper way to handle these is by dispatching a SideEffect.
The Store will execute the following operations:
- it executes the interceptors
- it executes the
SideEffectbody - it resolves the promise
Listen for updates
You can attach a listener that is invoked every time the state changes by using addListener.
Intercept Dispatchable
It is possible to intercept and reshape the behaviour of the Store by using a StoreInterceptor.
A StoreInterceptor is executed every time something has dispatched, and before it is actually
managed by the Store. Here you implement behaviours like logging, blocking items before they’re executed
and even change dynamically which dispatchable items arrive to the Store itself.
See also
StateUpdater for more information about how to implement an update of the state
See also
SideEffect for more information about how to implement a complex/asynchronous logic
-
Closure that is used to initialize the dependencies
Declaration
Swift
public typealias DependenciesInitializer = (_ dispatch: @escaping AnyDispatch, _ getState: @escaping StateInitializer<S>) -> D -
Whether the store is ready to execute operations
Declaration
Swift
public private(set) var isReady: Bool { get } -
The dependencies used in the side effects
See also
SideEffectDeclaration
Swift
public var dependencies: D! -
A convenience init method. The store won’t have middleware nor dependencies for the side effects. The state will be created using the default init of the state
Declaration
Swift
public convenience init()Return Value
An instance of store
-
A convenience init method for the Store. The initial state will be created using the default init of the state type.
Declaration
Swift
public convenience init(interceptors: [StoreInterceptor])Parameters
interceptorsa list of interceptors that are executed every time something is dispatched
Return Value
An instance of the store
-
A convenience init method for the Store. The dependencies will be created using the default init of the dependency type.
Declaration
Swift
public convenience init( interceptors: [StoreInterceptor], stateInitializer: @escaping StateInitializer<S>, configuration: Configuration = .init() )Parameters
interceptorsa list of interceptors that are executed every time something is dispatched
stateInitializera closure invoked to define the first state’s value
configurationthe configuration needed by the store to start properly
Return Value
An instance of the store
-
The default init method for the Store.
Initial phases
When the store is created, it doesn’t immediately start to handle dispatched items. Before that, in fact, the
Storewill (in order)- create the dependencies
- create the first state version by using the given
stateInitializer - initialise the interceptors
Accessing the state before the
Storeis ready will lead to a crash of the application, as the state of the system is not well defined. You can check whether theStoreis ready by leveraging theisReadyproperty.A good practice in case you have to interact with the
Store(e.g., get the state) in the initial phases of your application is to dispatch aSideEffect. When dispatching something, in fact, theStoreguarantees that items are managed only after that theStoreis ready. Items dispatched during the initialization are suspended and resumed as soon as theStoreis ready.Declaration
Swift
public init( interceptors: [StoreInterceptor], stateInitializer: @escaping StateInitializer<S>, dependenciesInitializer: @escaping DependenciesInitializer, configuration: Configuration = .init() )Parameters
interceptorsa list of interceptors that are executed every time something is dispatched
stateInitializera closure invoked to define the first state’s value
dependenciesInitializera closure invoked to instantiate the dependencies
configurationthe configuration needed by the store to start properly
Return Value
An instance of store
-
Adds a listener to the store. A listener is basically a closure that is invoked every time the Store’s state changes. The listener is always invoked in the main queue
Declaration
Swift
override public func addAnyListener(_ listener: @escaping AnyStoreListener) -> StoreUnsubscribeParameters
listenerthe listener closure
Return Value
a closure that can be used to remove the listener
-
Adds a typed listener to the store. A listener is basically a closure that is invoked every time the Store’s state changes
Declaration
Swift
override public func addListener(_ listener: @escaping StoreListener<S>) -> StoreUnsubscribeParameters
listenerthe listener closure
Return Value
a closure that can be used to remove the listener
-
Dispatches a
ReturningSideEffectitemThreading
The
Storefollows strict rules about the parallelism with which dispatched items are handled. At the same time, it tries to leverages as much as possible the modern multi-core systems that our devices offer.When a
ReturningSideEffectis dispatched, Katana will handle them in a parallel queue. AReturningSideEffectis executed and considered done when its body finishes to be executed. This means that side effects are not guaranteed to be run in isolation, and you should take into account the fact that multiple side effects can run at the same time. This decision has been taken to greatly improve the performances of the system. Overall, this should not be a problem as you cannot really change the state of the system (that is, the store’s state) without dispatching aReturningSideEffect.Promise Resolution
When it comes to
ReturningSideEffects, the promise is resolved when the body of theReturningSideEffectis executed entirely (seeReturningSideEffectdocumentation for more information).Declaration
Swift
@discardableResult override public func dispatch<T>(_ dispatchable: T) -> Promise<T.ReturnValue> where T : ReturningSideEffectParameters
dispatchablethe side effect to dispatch
Return Value
a promise parameterized to SideEffect’s return value, that is resolved when the SideEffect is handled by the store
-
Dispatches a
AnySideEffectitemThreading
The
Storefollows strict rules about the parallelism with which dispatched items are handled. At the same time, it tries to leverages as much as possible the modern multi-core systems that our devices offer.When a
AnySideEffectis dispatched, Katana will handle them in a parallel queue. AAnySideEffectis executed and considered done when its body finishes to be executed. This means that side effects are not guaranteed to be run in isolation, and you should take into account the fact that multiple side effects can run at the same time. This decision has been taken to greatly improve the performances of the system. Overall, this should not be a problem as you cannot really change the state of the system (that is, the store’s state) without dispatching aAnySideEffect.Promise Resolution
When it comes to
AnySideEffects, the promise is resolved when the body of theAnySideEffectis executed entirely (seeAnySideEffectdocumentation for more information).Declaration
Swift
@discardableResult override public func dispatch<T>(_ dispatchable: T) -> Promise<Void> where T : AnySideEffectParameters
dispatchablethe side effect to dispatch
Return Value
a promise parameterized to SideEffect’s return value, that is resolved when the SideEffect is handled by the store
-
Dispatches a
DispatchableitemThreading
The
Storefollows strict rules about the parallelism with which dispatched items are handled. At the same time, it tries to leverages as much as possible the modern multi-core systems that our devices offer.When an
AnyStateUpdateris dispatched, the Store enqueues it in a serial and synchronous queue. This means that the Store executes one update of the state at the time, following the order in which it has received them. This is done to guarantee the predictability of the changes to the state and avoid any race condition. In general, using a synchronous queue is never a big problem as any operation that goes in anAnyStateUpdateris very lightweight.Promise Resolution
When it comes to
AnyStateUpdater, the promise is resolved when the state is updated.Declaration
Swift
@discardableResult override public func dispatch<T>(_ dispatchable: T) -> Promise<Void> where T : AnyStateUpdaterParameters
dispatchablethe state updater to dispatch
Return Value
a promise parameterized to void that is resolved when the state updater is handled by the store
-
Dispatches a
DispatchableitemThreading
The
Storefollows strict rules about the parallelism with which dispatched items are handled. At the same time, it tries to leverages as much as possible the modern multi-core systems that our devices offer.When a
StateUpdateris dispatched, the Store enqueues it in a serial and synchronous queue. This means that the Store executes one update of the state at the time, following the order in which it has received them. This is done to guarantee the predictability of the changes to the state and avoid any race condition. In general, using a synchronous queue is never a big problem as any operation that goes in aStateUpdateris very lightweight.When it comes to
SideEffectitems, Katana will handle them in a parallel queue. ASideEffectis executed and considered done when its body finishes to be executed. This means that side effects are not guaranteed to be run in isolation, and you should take into account the fact that multiple side effects can run at the same time. This decision has been taken to greatly improve the performances of the system. Overall, this should not be a problem as you cannot really change the state of the system (that is, the store’s state) without dispatching aStateUpdater.Promise Resolution
When it comes to
StateUpdater, the promise is resolved when the state is updated. ForSideEffect, the promise is resolved when the body of theSideEffectis executed entirely (seeSideEffectdocumentation for more information).Declaration
Swift
@discardableResult override public func anyDispatch(_ dispatchable: Dispatchable) -> Promise<Any>Parameters
dispatchablethe dispatchable to dispatch, it can be either a StateUpdater or a SideEffect
Return Value
a promise that is resolved when the dispatchable is handled by the store
-
The dependencies used to initialize katana
See moreDeclaration
Swift
public struct Configuration
View on GitHub
Store Class Reference