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
updateState
function 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
SideEffect
body - 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
SideEffect
Declaration
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
interceptors
a 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
interceptors
a list of interceptors that are executed every time something is dispatched
stateInitializer
a closure invoked to define the first state’s value
configuration
the 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
Store
will (in order)- create the dependencies
- create the first state version by using the given
stateInitializer
- initialise the interceptors
Accessing the state before the
Store
is ready will lead to a crash of the application, as the state of the system is not well defined. You can check whether theStore
is ready by leveraging theisReady
property.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, theStore
guarantees that items are managed only after that theStore
is ready. Items dispatched during the initialization are suspended and resumed as soon as theStore
is ready.Declaration
Swift
public init( interceptors: [StoreInterceptor], stateInitializer: @escaping StateInitializer<S>, dependenciesInitializer: @escaping DependenciesInitializer, configuration: Configuration = .init() )
Parameters
interceptors
a list of interceptors that are executed every time something is dispatched
stateInitializer
a closure invoked to define the first state’s value
dependenciesInitializer
a closure invoked to instantiate the dependencies
configuration
the 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) -> StoreUnsubscribe
Parameters
listener
the 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>) -> StoreUnsubscribe
Parameters
listener
the listener closure
Return Value
a closure that can be used to remove the listener
-
Dispatches a
ReturningSideEffect
itemThreading
The
Store
follows 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
ReturningSideEffect
is dispatched, Katana will handle them in a parallel queue. AReturningSideEffect
is 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
ReturningSideEffect
s, the promise is resolved when the body of theReturningSideEffect
is executed entirely (seeReturningSideEffect
documentation for more information).Declaration
Swift
@discardableResult override public func dispatch<T>(_ dispatchable: T) -> Promise<T.ReturnValue> where T : ReturningSideEffect
Parameters
dispatchable
the 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
AnySideEffect
itemThreading
The
Store
follows 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
AnySideEffect
is dispatched, Katana will handle them in a parallel queue. AAnySideEffect
is 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
AnySideEffect
s, the promise is resolved when the body of theAnySideEffect
is executed entirely (seeAnySideEffect
documentation for more information).Declaration
Swift
@discardableResult override public func dispatch<T>(_ dispatchable: T) -> Promise<Void> where T : AnySideEffect
Parameters
dispatchable
the 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
Dispatchable
itemThreading
The
Store
follows 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
AnyStateUpdater
is 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 anAnyStateUpdater
is 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 : AnyStateUpdater
Parameters
dispatchable
the 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
Dispatchable
itemThreading
The
Store
follows 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
StateUpdater
is 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 aStateUpdater
is very lightweight.When it comes to
SideEffect
items, Katana will handle them in a parallel queue. ASideEffect
is 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 theSideEffect
is executed entirely (seeSideEffect
documentation for more information).Declaration
Swift
@discardableResult override public func anyDispatch(_ dispatchable: Dispatchable) -> Promise<Any>
Parameters
dispatchable
the 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