Redux + Swift
I started working with Redux in 2020 for the first time. When I first started with it in an iOS project, I did not understand the various components of Redux and RxSwift. But over a period of time, I did like this architecture.
Components of the architecture
Store
Reducer
Action
ActionHandler
Middlerware
SideEffects
The main philosophy behind the popularity of REDUX, IMO, is having a single source of truth for state and separation of concern.
Store, ActionHandler, Reducer, Middleware and SideEffects
are responsible for different actions.
Store in this architecture is a type that has a reference toActionHandler
, Reducer
, MiddlerWare
, and SideEffects,
and Store
calls reducer
when ActionHandler
done making the changes. So, from an app perspective, the app does not need to decide on when to call state mutation logic, its the store
which will call reducer
to perform that operation.
Types
Action
is a type that defines all the events that can happen during the lifetime of an app/view etc.
In a typical use case where there could two events for an app, launchApp, and launchedApp.
enum AppActions: Action { case launchApp
case launchedApp}
ActionHandler
is a protocol in this design pattern that when implemented can take care of making the async calls for the feature. It's not a networking layer but it's a type that encapsulates all the async operations/core data/user defaults operations etc. ActionHandler
notifies Store
after the async calls are complete with success or failure and it does it through subscription
as done here.
We have an important line here. Let’s see what it's doing, Actions
are by nature async but there is a sequence that matters functionality wise such as, in a typical view that appears/updates on the screen after a network call. First, we make a network call -> show a spinner -> then update the UI for success or failure. That's the whole point of this buffer, it stores the next action that should come in the sequence or chain of events.
Now, with that, we have covered two important types one that defines action and the other that implements what to do about those actions.
Let’s take a look at Reducer.
The Reducer
takes care of actually mutating the state of the app. Now, when a single type takes care of state mutation we don’t need to worry about various floating flags for various purposes, and view/logic can react to the changes in the state easily.
That was Reducer
which has an important place in this architecture since its responsible for state mutation and SideEffects
. We talked about state mutation, let’s look quickly on SideEffects.
It’s a type that is the outcome of the state mutation. SideEffects
is an enum in this implementation since there can be many events for different state changes.
Lastly, we have Middleware
, It’s a type that’s typically used to perform any action that is needed after actions are handled and state mutation is complete.
Implementation Details
A typical store
implementation (based on RxSwift)
Let's talk about what's happening in this type
It contains references to the type we talked about before and an init block to initialize and then an important function called dispatchActions, dispatchAction and onComplete.
Everything else here is pretty typical, so, let's focus on these 3
as these drives the changes in the state which drive changes in the view.
When a typical view gets an event it will call the store dispatchActions
and then the store will internally take care of state mutation, if any, by callingreducer-> middleware -> sideeffects
and subsequent actions. Take a look at the flowchart, in which, view (Action origination point) -> store
which contains all these components.
Since a single source of action, which typically is your view (for example), can generate many actions like a button tap, view update an API call, etc so adding s
at the end of action
makes sense.
Next, we have dispatchAction
that gets called for individual action
. This function calls ActionHandler
which is a type that takes care of calling any async calls. Typically, your API calls would go here. Once the response comes back your store will be notified since it has attached a subscription
to the changes as shown here https://gist.github.com/singh88/0817fd750d2440257a69283e1fd6d99c#file-defaultrxstore-swift-L56.
Next and the last topic in this discussion here is onComplete.
This function is important in subscription
both error and successful cases.
I think this could be a good stopping point for this part. In this part, we discussed a bit of Redux, talked about its high-level components of it, and got a little bit into the implementation details. You can look into the full implementation here.