Architecture is a fundamental part of your app. It’s what you build your app on top of. You should think about the architecture before you begin development and keep in mind both the technical and business purposes your iOS app will serve in the future. Choosing an architecture that’s clear and simple yet able to scale is a real challenge, and as an iOS developer, I feel that traditional iOS architectural patterns are sometimes hard to work with.

Introduction

I’m Dmytro, a senior iOS developer at Mobindustry. I’ve been working as an iOS developer for over five years, and during this time I’ve designed architectures for lots of projects. The more I’ve learned about architectures, the more I’ve realized that none of them is perfect.

Luckily, there are architectures that can be used to solve some issues and make your basic architecture better. Since developers usually don’t choose only one architectural pattern but rather combine ideas from different architectures, in this article I’d like to show you how to use the principles of Redux to get rid of some problems in traditional architectural patterns like MVC and MVVM.

Let’s talk about MVC

Before I even start, I’d like to make a disclaimer: There are no good or bad solutions. For every case the solution is individual, and though each solution has its drawbacks, each is appropriate for a certain case to a certain extent.

Many developers consider Model–View–Controller (MVC) a bad architecture with lots of drawbacks, especially in the context of iOS development. But is it really? MVC is a simple and easy to understand architecture, familiar to every iOS developer thanks to Apple’s and Stanford’s free iOS development courses. If you know how to develop apps for iPhones, you know MVC for sure.

I’ll just remind you briefly of its principle, and then we’ll discuss its pros and cons.

The MVC architecture is as simple as its name. It consists of three components:

  • The Model
  • The View
  • The Controller

The Model stores all the data that your app deals with. This includes model objects, networking code, persistence objects, and parsers.

The View is what the user sees. It contains UI elements that are usually reusable because there’s no specific logic in them. Views that present text, buttons, images, and so on are stored in the View.

The Controller is a mediator between the Model and the View. It stores all business logic of the application and connects the View and the data in the Model via protocols.

This elegant and simple structure allows you to develop a simple application quickly. I’m sure that MVC is the best choice for your MVP because it’s simple, clear, fast to develop, and easy to understand for any developer who’s new to the project.

mvc ios architecture
This is how the elements of the MVC architecture are related to each other

The problems with MVC-based apps begin as time passes. The codebase grows, and the complexity grows as well. Even if you’re an experienced developer and you’re being careful, at some point you’ll find yourself fixing a callback hell in one of hundreds of Massive View Controllers.

You’ll have no idea where all the app data is located and how you’re supposed to pass it from one part of the app to another. You’ll have no idea what piece of code is responsible for what and how to call logic without creating hidden instances of some View Controller. It’s very easy to lose control of an application written with MVC as it grows.

Let’s talk about MVVM

What most developers do to avoid the drawbacks of the MVC architecture is turn to the Model–View–ViewModel (MVVM) pattern. MVVM is better at separating logic and data, so it’s a great choice to implement the thin controller, fat model concept.

What is MVVM, and what do you get when you use it for your iOS app architecture?

MVVM also has three layers:

  • The Model
  • The View
  • The ViewModel

The Model is a conglomerate of classes that form the data and business logic. The ViewModel gathers data from the Model and represents it to the View Controllers. The ViewModel also reacts to user actions, referring to the business logic and showing the response through the View. View Controllers that bind Views with ViewModels organize the navigation around the app. This is how the MVVM pattern works.

There are fewer problems with MVVM than with MVC because at least we now know what’s responsible for what and where our data is located. Or do we really? Let’s see… It’s stored across lots of services, controllers, helpers, and workers in MVVM. At least all of this is still stored in something called the Model, and not in the View Controller. The logic is in the Model as well.

MVVM is slightly more complex than MVC, but it still has some issues with order

In MVVM, you’ll still have lots of ViewModels that call different methods in different parts of the Model in a random order. Because of this, you still don’t always know what logic belongs to the ViewModel and what’s better to move to some service. You also can’t be sure that a ViewModel won’t be deleted before hitting a callback at the end of a certain task. Still, with MVVM we have some sort of order in our app.

As you can see, both MVVM and MVC have drawbacks that can turn your app code into a mess. This is what happened to Facebook in 2014, and their development team decided to come up with a better way to design their app’s architecture. As a result, in 2015, Flux was presented. Later, Flux inspired two other developers, Dan Abramov and Andrew Clark, to create Redux. Let’s talk about Flux and Redux and find out how to implement a Redux architecture using Swift.

Let’s talk about Flux and Redux

Flux was made to solve the lack of clarity of the two other types of architectures for iOS: MVC and MVVM. A year after Flux was introduced, Redux appeared, developed on JavaScript. Here we’ll see how to develop with Redux in Swift.

Redux is a JavaScript library that’s used for application state management. Redux is based on three principles:

  • Unidirectional data flow
  • A state as a single source of truth
  • Immutability of a state

Unidirectional data flow determines the way the data flow is organized. According to this principle, user interactions and internal events go as Actions to the Store, the Store dispatches them to Reducer(s), and then the Store propagates the result as a State to all its subscribers.

The State as a single source of truth means there’s an entity called a State that contains all the app data. Thus, everything we need is always there.

Immutability of State means that only the Store can change the State. That’s why nobody can corrupt the data.

The represantation of all core elements of Redux and their relations

The main element in the Redux architecture is the Store. It handles Actions using Reducers, while also changing and propagating the State. An Action is just a data structure that conforms to the Action protocol and contains any necessary external data, such as a login and password for the Login action. However, Action contains nothing for Logout.

protocol Action {}
 
struct Login: Action {
    var login: String
    var password: String
}
 
struct Logout: Action {}

A State is also a data structure that contains all app data, for example authentication status, credentials, loading status, and possible errors.

protocol State {}
 
struct LoginState: State {
    var loading: Bool
    var token: String?
    var error: Error?
}

A Reducer is what handles Actions and performs work. A Reducer can do all this by itself or by delegating it to services. As a result, it returns the changed state.

Start with protocol as usual:

protocol Reducer: class {
    func reduce(action: Action, state: State) -> State
    init()
}

This is a simple class I’ve written to demonstrate how this works:

class LoginReducer: Reducer {
    let network: Network
    
    required init() {
        self.network = Network()
    }
    
    func reduce(action: Action, state: State) -> State {
        guard state is LoginState { return state }
        var state = state as! LoginState
        
        if let action = action as? Login {
            state.loading = true
            state.error = nil
            network.login(action.login, action.password)
        }
        else if action is Logout {
            state.loading = false
            state.token = nil
            state.error = nil
        }
        
        return state
    }
}

Finally, there’s the Store. The Store must be able to handle actions and notify subscribers about a new state. Here’s how you should go about it:

func handle(action: Action) {
    self.state = self.reducer.reduce(action: action, state: self.state)
    for subscriber in self.subscribers {
        subscriber.newState(self.state)
    }
}

The final thing we need is a subscriber protocol. It should be implemented by everyone who wants to receive state updates.

protocol Subscriber: class {
    func newState(_ state: State)
}

In the end, this is how part of our imaginary LoginViewController looks:

func newState(_ state: State) {
    // Here is the place for UI updates
}
 
func login() {
    // Here we create action for login and dispatch it to the store
    let action = LoginAction(loginTextField.text, passwordTextField.text)
    store.handle(action)
}

This is all you need to use Redux for creating an architecture for your application. Let’s sum up.

If you want to implement logic, you need to create an Action and send it to the Store. If you want to get updates on the app’s state, you should subscribe to the Store and implement one single method. This will allow you to receive updates.

Everything works like this, and these relations determine the way to build anything in your app. As the app grows, you’ll need to learn to manage the growing complexity of the State and reducers, asynchronous tasks, and repeating state updates.

If you have any questions or if you’re struggling to find the right architecture for your iOS app, feel free to contact Mobindustry. We’ll give you a consultation and offer a solution suitable for your project.

iOS development services

Do you need a team of professional developers you can lean on? We are at you disposal

Get a Free Consultation!
Request Callback

Request Callback

+