paint-brush
A Look at the MVVM Design Patterns for iOSby@maxkalik
843 reads
843 reads

A Look at the MVVM Design Patterns for iOS

by Max KalikNovember 7th, 2022
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Popular architecture pattern for iOS applications is MVVM + Coordinators. Almost 30% of medium and large applications are using this architecture more-less. Common Dependencies object should be opened for inheritance from the applications which are also can have some services which will lay together with common services. Common Framework is our focus and there we will prepare a foundation. We will structure the Common framework where we will create four groups: Views, Base Controllers, Protocols, and Services. The property appId will allow identifying an app which are using the services. It’s helpful for some requests that should contain app id in the paths.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - A Look at the MVVM Design Patterns for iOS
Max Kalik HackerNoon profile picture

Let me add my couple of cents about iOS Design Patterns. Recently I researched (and personally experienced) and was surprised that one of the most popular architecture patterns for iOS applications is MVVM + Coordinators, which means almost 30% of medium and large applications are using this architecture more-less. Yes, MVC is one of the most usable too, but I’m talking about applications like financial instruments, mobile banks, educational apps, shops, delivery apps, and more. It’s understandable why, supporting more complicated, let’s say VIPER, is more expensive because businesses want to see a new feature in production asap. After trying to keep code cleaner, engineers come to the MVVM just because this pattern is more convenient and flexible for scale.

As a software engineer I have the energy to contribute to the community and who knows maybe this particular example will be useful for the iOS community.

Here in this text, I will try to reduce abstractions because from my experience I see sometimes it doesn’t work well to explain complicated things. I will just show you a simplified version. If you are reading this text, you are most likely already experienced guys, you know the basics and are ready to digest and discuss information about at least clean coding approaches. That’s what I want from you after reading this article.

App family

To be more precise about what we are going to do, we need to consider a situation: let’s say we have a family of iOS applications (2 or more applications).

- App One
- App Two
- ...

All these applications are different but with the same UI components, screens, behaviors, and features. And our task will be to design an architecture where all these pieces will be independent and sharable.

After identifying the components which are sharable we can move them into the framework. Let’s call it Common.

So, this framework is our focus and there we will prepare a foundation.

Framework

Let’s structure the Common framework where we will create four groups: Views, Base Controllers, Protocols, and Services. 

Common
├── Views
├── BaseControllers
├── Protocols
└── Services

In Views, the folder contains only ready-to-use

UIViews
that could be used everywhere in the apps. In our case, we put there ProgressView and BaseButton.

BaseControllers
folder contains View Controllers for inheritance. For example,
BaseViewController
, and
BaseHostingController
for SiwftUI views, and
BaseNavigationController
. All these base controllers already have a property of
ProgressView
which can be started and stopped spinning.

The protocols folder consists of all protocols which are common, for example,

BaseViewModel
,
Coordinator
, and so on. We will figure out about them later.

Services. Let’s say our family applications use some services and part of them are sharable, for example,

CommonServiceOne
and
CommonServiceTwo
.

Dependencies

Designing dependencies in our case means we will combine all our services into one place and this place will be reachable and can be configurable from all view models. The main idea is that our Common Dependencies object should be opened for inheritance from the applications which are also can have some services which will lay together with common services.

As you already know we have two common services. Let’s create two protocols for them:

HasCommonServiceOne
and
HasCommonServiceTwo
.

As you can see protocol Dependencies are inherited from the two protocols that require to have the services. The property appId will allow identifying an app which are using the services. It’s helpful for some requests that should contain app id in the paths.

Finally, we have a class that conformed Dependencies protocol with initialized our two services.

Regarding the services, they have just two methods that print the line with the app id:

CommonServiceOne

func commonServiceOneMethod() {
    print("== Common Service 1 method fired from \(appId)")
}

CommonServiceTwo

func commonServiceOneMethod() {
    print("== Common Service 2 method fired from \(appId)")
}

So we will use it later to understand from where we can access them.

Dependencies should be accessible from view controllers so for that we need an additional protocol that can be named

WithDelegates
:

Coordinator

The protocol Coordinator has typical requirements for any coordinators in the projects. It means it can be also in a common framework. Let’s take a look at what is in there:

If you are familiar with the Coordinator design pattern then you should know already why we need these methods. Super easy,

start() 
— for starting the coordinator with an initialized view controller, and
finish()
 — for poping view controller from the navigation stack. As you can see this protocol has an extension where we can predefine this behavior for both methods.

BaseController

We need an additional protocol to combine two View Controllers:

BaseViewController
and
BaseHostingController
. As you know they have
ProgressView
, and two methods that can start and stop spinning. We are going to move these methods and define requirements for the Base Controllers.

BaseViewModel

Of course, if the apps

haveViewModels
then we need to identify common methods. Let’s say it will be:
prepareViewModel()
. Additionally, to interact and delegate some jobs to the
ViewControllers
we need to have a
viewDelegate
.

Base Delegates

Two delegates. The first one is for View Controllers and the second one is for Coordinators. Let’s take a look at them.

BaseViewModelViewDelegate

Using this protocol for all

viewDelegates
we will allow to show and hide activity indicators without repeating these methods in app’s
viewModelViewDelegates
.

BaseCoordinatorDelegte

A similar approach in

coordinatorDelegate
.

BaseViewController and BaseHostingController

BaseController
protocol combines two
ViewControllers
where
viewModel
is
BaseViewModel
.

Application

All basic components of our framework are done, so it’s time to use them in our applications. It's time to define an app structure:

Based on the schema above, we need to prepare:

AppCoordiantor
, AppDependencies, Home Module, SignIn Module. Using the prepared Common Framework we can use the protocols from there to prepare all these pieces of the app.

App Dependencies and services

Let’s say an application has its own services and we need to combine them with shared services. For that our protocol Dependencies and

CommonDependecies
opened class will help us.

As you can see additional app services are initialized in the same way as in the common dependencies. So, for now, our

AppDependencies
has 4 services that we can use across the app.

App Coordinator

In this implementation, you can see how we can use

WithDependencies
protocol.
AppDependencies
will be used in all modules:
Home
and
SignIn
. Also, take a look at
HomeViewModel
coordinator delegate and how it is used. To push the view controller to the stack it’s enough to use only
show()
method that already exists in
Coordinator
protocol.

View Models

The most interesting part is using view models. Again, our view models don’t know about the view and coordinator, that’s why they are independent. Common protocols:

BaseViewModel
and
WithDependencies
help to encapsulate an implementation where we can potentially use dependencies.

Also in

prepareViewModel()
we can use directly some
commonServieMethod()
that is defined already in
BaseViewModel
. But, wait, BaseViewModel doesn’t use protocol
WithDependencies
. So, let’s say we need to use it in all our applications from all
viewModel
. For that we need to update
BaseViewModel
in Framework using this way:

This means the

methodsomeCommonServiceMethod()
can be used only from View models but not from the views.

Additionally, if you don’t need all dependencies in the view model you can specify the particular ones:

final class SignInViewModelImplementation<Dependeincies: HasAppServiceOne & HasAppServiceTwo>: SecondaryViewModel, WithDependencies {
}

What about family?

An idea is simple - using the protocols and the classes with associated types from the Common framework your next app with MVVM + Coordinator will be structured in the same way as the first one but your modules and behavior can be different.

The services, coordinator, and base controller will take common responsibility. In this example, it's only showing and hiding activity indicators, but in real applications, you could have a lot of staff. For instance, shared localization resources and a service for managing the strings by a key that can be used from all applications in family.

Wrapping Up

Recently I had a problem when I needed to prepare a framework with sharable components for multiple applications. Making this kind of framework allowed me to move maintainability to the next level. It means we could scale our projects and this framework without discussing it. Everyone from the team understood where and what they need first before starting a new feature.

As you noticed the barrier of entry is really low because you don’t need to know specific things, everything implements in a swifty way. Also, it can be applied with RxSwift or Combine as well.

Thanks for reading!

Source Code