Classes
The following classes are available globally.
-
Easiest way to implement a
Middleware
, with a single function that gives you all you need, and from which you can return anEffect
.A
MiddlewareEffect
is a function providing an incomingAction
,State
andContext
(dispatcher source, dependencies, cancellation closure) and expecting as result one or multiple effects that will eventually result in outgoing actions.An
Effect
is a publisher or observable type according to your reactive framework. It can be a one-shot effect, such as an HTTP request, an observation that gives back multiple values over time, such as CoreLocation or NotificationCenter, a Timer, a pure value already known or even an empty effect using the.doNothing
constructor, if there’s no need for side-effects.Effect
cannot fail, and its element/output/value type is theOutputAction
generic of this middleware. The purpose is running tasks, creating actions as they respond and returning these actions over time back to the Store. When the Effect completes, it should send a completion event so the middleware will cleanup the resources.Cancellation: effects can optionally provide a cancellation token, which can be any hashable value. If another effect arrives in the same middleware instance having the very same cancellation token, the previous effect will be cancelled and replaced by the new one. This is useful in case you want to keep only the last request of certain kind running, but cancel any previous ongoing request when a new is dispatched. You can also explicitly cancel one or many effects at any point. For that, you will be given a
toCancel
closure during every action arrival within theContext
(third parameter). Feel free to call cancellation at that point or even later, if you hold thistoCancel
closure.Examples
Using Promises
let someMiddleware = EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, Void>.onAction { action, state, context in switch action { case .users: return .promise { completion in DispatchQueue.main.asyncAfter(deadline: .now() + 3) { completion(ApiResponseAction.someResponse("42")) } } case .somethingIDontCare: return .doNothing } }
From Publisher
typealias ApiFetchMiddlewareDependencies = (session: @escaping () -> URLSession, decoder: @escaping () -> JSONDecoder) let apiFetchMiddleware = EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, ApiFetchMiddlewareDependencies>.onAction { action, state, context in switch action { case .users: return context.dependencies.urlSession .dataTaskPublisher(for: fetchAllUsersURL()) .map { data, _ in data } .decode(type: [User].self, decoder: context.dependencies) .map { users in ApiResponseAction.gotUserList(users) } .replaceError(with: ApiResponseAction.errorRetrivingUserList) .asEffect case .user(id: UUID): // .. case .somethingIDontCare: return .doNothing } }.inject((session: { URLSession.shared }, decoder: JSONDecoder.init))
Cancellation
See moretypealias ApiFetchMiddlewareDependencies = (session: @escaping () -> URLSession, decoder: @escaping () -> JSONDecoder) let apiFetchMiddleware = EffectMiddleware<ApiRequestAction, ApiResponseAction, SomeState, ApiFetchMiddlewareDependencies>.onAction { action, state, context in switch action { case let .userPicture(userId): return context.dependencies.urlSession .dataTaskPublisher(for: fetchPicture()) .map { data, _ in ApiResponseAction.gotUserPicture(id: userId, data: data) } .replaceError(with: ApiResponseAction.errorRetrivingUserPicture(id: userId)) .asEffect(cancellationToken: "image-for-user-\(userId)") // this will automatically cancel any pending download for the same image // using the URL would also be possible case let .cancelImageDownload(userId): return context.toCancel("image-for-user-\(userId)") // alternatively you can explicitly cancel tasks by token } }.inject((session: { URLSession.shared }, decoder: JSONDecoder.init))
Declaration
Swift
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public final class EffectMiddleware<InputActionType, OutputActionType, StateType, Dependencies> : MiddlewareProtocol
extension EffectMiddleware: Semigroup
extension EffectMiddleware: Monoid where Dependencies == Void
-
A Store Projection made to be used in SwiftUI
All you need is to create an instance of this class by projecting the main store and providing maps for state and actions. For the consumers, it will act as a real Store, but in fact it’s only a proxy to the main store but working in types more close to what a View should know, instead of working on global domain.
See more┌────────┐ │ Button │────────┐ └────────┘ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┌──────────────────┐ │ dispatch ┃ ┃░ │ Toggle │───┼────────────────────▶│ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─▶ │────────────▶┃ ┃░ └──────────────────┘ │ view event f: (Event) → Action app action ┃ ┃░ ┌──────────┐ │ │ │ ┃ ┃░ │ onAppear │───────┘ ┃ ┃░ └──────────┘ │ ObservableViewModel │ ┃ ┃░ ┃ ┃░ │ a projection of │ projection ┃ Store ┃░ the actual store ┃ ┃░ │ │ ┃ ┃░ ┌────────────────────────┐ ┃ ┃░ │ │ │ │ ┌┃─ ─ ─ ─ ─ ┐ ┃░ │ @ObservedObject │◀ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ◀─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ◀─ ─ ─ ─ ─ ─ State ┃░ │ │ view state │ f: (State) → View │ app state │ Publisher │ ┃░ └────────────────────────┘ State ┳ ─ ─ ─ ─ ─ ┃░ │ │ │ └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ┗━━━━━━━━━━━━━━━━━━━━━━━┛░ ▼ ▼ ▼ ░░░░░░░░░░░░░░░░░░░░░░░░░ ┌────────┐ ┌────────┐ ┌────────┐ │ Text │ │ List │ │ForEach │ └────────┘ └────────┘ └────────┘
Declaration
Swift
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) open class ObservableViewModel<ViewAction, ViewState> : StoreType, ObservableObject