Architecture Thoughts: Migrating Marvel's iOS App to ReSwift ..
A couple days ago I've read a great article called Architecture Wars at swifting.io's site. In short the article was presenting several different architecture patterns used in iOS, pros and cons, and so on. Software architecture by itself is a very large topic with many branches. The article gave me some ideas for future posts, in which I would explore some of those architecture patterns and refactor our Marvel's app to conform with them.
ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift. ReSwift helps you to separate three important concerns of your app’s components:
State: in a ReSwift app the entire app state is explicitly stored in a data structure. This helps avoid complicated state management code, enables better debugging and has many, many more benefits…
Views: in a ReSwift app your views update when your state changes. Your views become simple visualizations of the current app state.
State Changes: in a ReSwift app you can only perform state changes through actions. Actions are small pieces of data that describe a state change. By drastically limiting the way state can be mutated, your app becomes easier to understand and it gets easier to work with many collaborators. — From it's Github Page.
Many iOS developers are familiar with the problem of the "Massive View Controller" - a component that clearly oversteps…realm.io
Through this post I'll show some of the modifications that I've made on my app to conform with ReSwift architecture, some of the lessons learned in the process and some personal opinions.
Nuff said … Show me some code !!
The application state is defined in a single data structure which should be a
structcan have other
structs as members, that allows you to add different sub-states as your app grows.
The first thing we need to do is create the AppState struct, this struct will be responsible for holding the entire application state.
Our app is quite simple so our struct only need to know about two things:
As you can see, i've created two other structs that conform with StateType protocol. The idea here is that AppState should hold the entire application state, as mentioned, but not by itself alone. It should derive it's state from many other specific sub-state structs.
Next we need to create a Store, this element will be responsible for glue everything together: state, actions, reducers, etc .. The Store will be used to subscribe/unsubscribe elements for state changes, dispatch actions that will be processed by the reducers, who will change aplication state.
The documentation and the code examples recommend the use of a global variable placed inside AppDelegate called store(Like e.g. Bellow) to accomplish this global visibility and uniqueness, but other ways are also possible, like singletons for instance.
Actions are used to express intended state changes. Actions don’t contain functions, instead they provide information about the intended state change, e.g. which user should be deleted. In your ReSwift app you will define actions for every possible state change that can happen. Reducers handle these actions and implement state changes based on the information they provide. All actions in ReSwift conform to the
Actionprotocol, which currently is just a marker protocol.
As mentioned above, actions are simple structs that contain only enough data needed by the reducers. If you think about reducers like functions the actions would be like parameters.
Reducers are the working beasts, they are responsible for doing all the hard work necessary to update the appState. The app usually has a main reducer that simply delegates the work for others, more specific reducers.
You can see bellow some of them in action.
One of the main advantages of adopting ReSwift, is that much of what was previously done inside your views and view controllers can migrate to reducers. The end result is pretty great and it allow our views and view controllers to simply dispatch actions using the store, and update the UI based on state change notifications.
You can see bellow the same view with the same behavior before and after adopting ReSwift:
Before our view was using RxSwift and talking directly with our Realm Manager. It is not a bad design, but our view still knows a little about implementation details. For instance if we stop using RxSwift or Realm we would have to change the view.
A refactored View
This ReSwift version, removed many of the implementation details. Now we only need to dispatch a few actions and subscribe for state changes, and the behavior still the same.
This was my first approach using ReSwift, there still a lot not covered here. ReSwift has others extensions that provide time travel capability, address routing and navigation. It's architecture is pretty straightforward, and tries to fight the mutation monster by limiting the scope where change can happen.
That said, for me it feels like few things still need to be improved. Such as:
- examples are still using too many global functions
- There should be a way to subscribe to only sub-state changes. Right now any action is first handled by the main Reducer, who delegates it to the appropriate reducer. For the smallest changes a new AppState struct is created, triggering State notification changes throughout the app.
- The Store variable inside the AppDelegate gotta go. Maybe a initialization process like analytics frameworks do, i'm not really sure what is the answer, many things can be done to address that, my concerce is more like a feeling, that somehow this variable feels misplaced inside the AppDelegate.
Last but not least, I really think you should get your hands dirty with ReSwift. It is the only way to find out if is the right architecture approach for you. Play with it and share your thoughts as a response or elsewhere. If you notice something that can be improved in the code, and would like to help, PR are more than welcome. =)
As always any thoughts, doubts or feedback are more than welcome. =)
Ps: If you like this post, share it on twitter, recommend it on medium, or both =). This really helps me to reach more people. Thanks a lot ..