My Issues With ReSwift
Background
ReSwift is a library for Swift based on Redux. Redux is a library that defines itself as 'a predictable state container for JavaScript apps.' The opening paragraph for the motivation behind creating the Redux library states that it is largely driven by the influx of single-page applications, which have increased requirements for state and which needs to be managed effectively between the client side state, ui state, service responses, and synchronizing with a server. Additionally they state that the key issues that they are trying to address with web development nowadays is how to effectively manage 'mutation and asynchronicity.' They note how React is a library that can be used to remove asychrony and direct DOM manipulation at the view level, but the task of managing data state still needs to be addressed, and that is the role that Redux fits.
So, back to ReSwift. On the ReSwift site, where they explain the role they are trying to fill, they note a few key concepts. One is the fact that MVC for Cocoa applications is not necessarily intuitive, and can create additional complexity because of the need to use callbacks, delegates, and notifications in order to pass stateful information through the app. The second main issue that they are trying to address is that developers can introduce inconsistent code because each developer might have a different preference on how to propagate state in the app. On top of these issues is the overarching concern that state can be a difficult thing to maintain and a framework to maintain it might help mitigate the most common issues seen while trying to maintain app sate.
When ReSwift is implemented, it manifests as a globally accessible AppState
object that lives in the AppDelegate. In order for state to be changed, an event has has to be dispatched to the AppState
, through a strongly typed Action
that enforces that the correct type of object is passed through. This object is then accessed in a `reducer` which executes code depending on the type of Action
and maintains an updated reference of the dispatched object on the globally accessible AppState
.
My Concerns
State management for mobile applications, particularly in iOS is a tricky thing, and its understandable why the ReSwift team is trying to introduce a solution for it. However, I think that there are more simple solutions for the problems they are trying to address, and the solutions they provide introduce new risks and problem areas.
One of their goals is to mitigate the use of complex systems like callbacks, delegates, and notifications in order to pass stateful information around the app. However, using RxSwift would mitigate this as well. The use of Observables
makes most usages of callbacks and delegates obsolete, and notifications are likewise converted to global Observables
that can be more simply observed and reacted to than the previous implementations requiring selectors. Similarly to the javascript utility of React.js, this use case shines when you have something like a ViewModel
that your ViewController
can be bound to, that is updated based on model changes hosted in the ViewModel
. However, there is also the ability to use RxSwift in independent classes that, for example, make a service call, update a model in a database, or similar instructions that need to be reacted to. Stateful information can be passed by an Observable
, which is observed by any group of code that needs to execute something based on the resulting state.
Additionally, they note that code inconsistency is an issue that they are trying to provide a solution for. They do make the note that "You can circumvent this issue by style guides and code reviews but you cannot automatically verify the adherence to these guidelines," and while it is generally true that a style guide and code review can't catch issues as quickly as a developer can write it, there are still ways to address this. For one, part of the style guide can be an architectural pattern for passing state as strongly typed models instead of weakly typed objects that are casted to the object that is needed at compile time. Additionally, these style guide can be enforced with rules in libraries like SwiftLint, which throw compiler warnings or errors, depending on configuration, in order to catch these issues at compile time, instead of waiting for a code review to enforce it.
State problems in iOS largely manifest as a result of state needing to be maintained over multiple screens, and ReSwift, which is based on Redux, is built around the idea of single-screen applications. In order to use Redux for an application with multiple screens, the navigation state needs to be preserved in the AppState
. This poses an additional problem, since the navigation stack is maintained internally by the NavigationController
, which belongs to the ViewController
. Currently, there is a standalone library by the ReSwift team called the ReSwift Router which is in a very early version at this time, and has some problems that have yet to be resolved, but they highlight some issues that pose significant quality issues.
As it is, the NavigationController
natively has a back button that pops the last ViewController
off of the stack. Unfortunately, this does not operate through the AppState
so the navigation stack maintained by the operating system becomes out of sync with what is being maintained by the AppState
. We could override the back button with a custom implementation that uses the AppState
to navigate, as per the pattern, but reimplementing a control provided by the Operating System, as well as maintaining a duplicate stack, should be an indication that the pattern is not appropriate for the use case.
Aside from these concerns, the architecture of the AppState
enforces globally accessible Singletons
, which has responsibilities associated with any and all screens in the application. The AppState
becomes responsible for maintaining app state variable, propagating those state changes to subscribers, invoking reducer logic, and maintaining the navigation stack. While you could look at these and say it all falls under the umbrella of 'maintaining state', the reality is that there isn't one large bucket that fits all instances of state management. State that needs to be maintained for an entire user session on multiple screens and state that is exists to update a component on a single screen should not be treated the same way. The scope of the AppState
quickly becomes a tremendous object with ambiguous responsibility, and as a result, it introduces maintainability concerns.
Conclusion
I think that if an application is a single screen, ReSwift can easily fit the objectives of the application. Likewise, if the application has a unidirectional flow, I could see some merit to using a library like this. Unfortunately, the second your app starts doing more complex flows, which may include redirecting, or allowing the user to navigate backwards or forwards at will, it introduces more logistical problems than i think ReSwift addresses. Redux was designed to address the issues of responding to lots of state changes on a single-screen web application, and when that pattern is ported into an environment like iOS where a large amount of the application state is maintained implicitly by the operating system itself, it creates a very clear logistical hurdle of manually trying to stay in sync with the same state that is being maintained by the operating system.