Recently, I’ve been more involved in front-end development than in mobile, and I came across some very interesting design patterns that I already knew, but didn’t really go into them ... until now.
But now all this makes sense, after using from development in React for several weeks, I can’t now return to my old iOS development methods. I will not switch to javascript (AKA React Native) for developing mobile applications, but here are some things I learned.
Returning to iOS development, I created a new project and started exploring
ReSwift , this is an implementation of the
Flux and
Redux pattern in Swift. And it works quite simply, I cloned the architecture of the JavaScript application several times, now I have a global state, and my controllers just listen to this state. The controllers themselves are made up of various presentation components that encapsulate very specific behavior.
All
state changes are made in one place, in
reducer . One for the substate. You can see all the
actions in one place. No more network code or calling controllers, no more mutations of objects in views. No more spaghetti code. There is only one
state , and it is true, then your various presentation components (and I insist on it) subscribe to different parts of
state and react accordingly. This is simply the best architecture for a strong model application.
For example. Previously, login view controllers were filled with a lot of lines of code, various control states, error handling, etc. ... Now it looks like this: (As an example)
import UIKit import Base import ReSwift class LoginViewController: UIViewController { @IBOutlet var usernameField: UITextField! @IBOutlet var passwordField: UITextField! override func viewDidLoad() { super.viewDidLoad() store.subscribe(self) {state in state.usersState } } @IBAction func onLoginButton(_ sender: Any) { store.dispatch(AuthenticatePassword(username: usernameField.text!, password: passwordField.text!)) } @IBAction func onTwitterButton(_ sender: Any) { store.dispatch(AuthenticateTwitter()) } @IBAction func onFacebookButton(_ sender: Any) { store.dispatch(AuthenticateFacebook(from: self)) } }
Controllers and representations of
dispatch actions in the global state, these actions actually work with the network or launch various parts that your application will need to convert to a new state.
An action can trigger another action, this is how it happens for a network request, for example, you have one
FetchUser action
(id: String) and one action that you intercept in a reducer that looks like SetUser (user: User). In reducer, you are responsible for merging / merging a new object with your current state.
First you need
state , my example will focus around the
User object, so
state might look something like this:
struct UsersState { var users: [String: User] = [:] }
You must have a file that encapsulates all network activities for the user object.
struct FetchUser: Action { init(user: String) { GETRequest(path: "users/\(user)").run { (response: APIResponse<UserJSON>) in store.dispatch(SetUser(user: response.object)) } } }
Once the request is completed, it calls another
action , this action is actually empty, it should be referenced, for example, in UsersActions. This action describes the result that reducer must rely on to change state.
struct SetUser: Action { let user: UserJSON? }
And finally, the most important work is done in
UsersReducer , you need to catch the action and do some work in accordance with its contents:
func usersReducer(state: UsersState?, action: Action) -> UsersState { var state = state ?? initialUsersState() switch action { case let action as SetUser: if let user = action.user { state.users[user.id] = User(json: user) } default: break } return state }
Now all that is needed is
suscribe / subscribe to the state in controllers or views, and when it changes, extract the necessary information and get new values!
class UserViewController: UIViewController { var userId: String? { didSet { if let id = userId { store.dispatch(FetchUser(user: id)) } } } var user: User? { didSet { if let user = user { setupViewUser(user: user) } } } override func viewDidLoad() { super.viewDidLoad() store.subscribe(self) {state in state.usersState } } func setupViewUser(user: User) {
But now you should take a look at the
ReSwift examples for a deeper understanding, I plan to publish an open source application (actually a game) using this design pattern. But for now, the code displays a very crude idea of how this all works together.
This is still a very early architecture in Glose books, but we cannot wait for the application to be put into production using this architecture.
I feel that developing applications using this pattern will save a lot of time and effort. It will take a little more work than a stupidly simple
REST client , because there will be a bit more logic inside the client state, but in the end it will save you invaluable time for debugging. You will be able to modify many elements locally, and there will no longer be cascading changes between controllers and views. Reproduce the state in backup order, archive it, create middleware, etc. The application data stream is clear, centralized, and simple.
The
Redux pattern adds a bit of structure to the application. I have been doing pure MVC for a very long time, I am sure that you can create a clean code base, but you tend to develop habits that often do more harm than good. You can even take one more step and fully implement Redux by controlling your user interface (such as view controllers, alert viewers, routing controllers) in a separate state, but I have not yet achieved all this).
And the tests ... Unit testing is now easy to implement, because all you need to test is to compare the data that you enter with the data that is contained in the global state, so the tests can send mock actions, and then check whether the state matches what you want to.
Seriously, this is the future. The future is for
Redux :)