iOS MVC Isn’t Bad Architecture

You are probably doing it in a wrong way

Artiom Khalilyaev
Level Up Coding

--

Photo by Sven Mieke on Unsplash

Model-View-Controller is a well-known architecture pattern introduced by the Apple long time ago when I was in a high school. You can find lots of articles saying that MVC is the worst architecture and there are a lot of others patterns way better than it. It’s true, there are a lot of good architecture patterns like MVP, MVVM, VIPER, MVI, TCA and many others. But the presence of these patterns doesn’t make MVC worse or better.

When you know how to implement it, you are able to build any app with ease. That app will be easy to update and maintain. And you won’t have a single Massive View Controller in your project.

What makes architecture a good architecture?

It must be easy to update, maintain and follow the separation of concerns principle. And that’s it! It also should be suitable for your purposes. If your app is completely state driven, MVVM, TCA or Redux is the better decision. But once again, any correctly implemented architecture is fine. The question is how to implement it?

To understand what makes MVC good MVC, let’s take a look at issues many developers are facing while working with it. The most well-know is Massive View Controller. If you’ve faced it, most probably your MVC doesn’t follow the separation of concerns any more, and it’s hard to update.

Avoiding Massive View Controller

First things first, View Controller != Controller, View Controller is a part of a View layer.

When I started coding with Swift, storyboard ans XIB files were still a popular way of building UI. And many coding tutorials were implementing MVC module using the following components:

All UI were created in a storyboard. Model logic was putted into external services depending on a data source, network requests were part of some API services, all database interactions were inside Database service. And all those parts were brought together inside View Controller.

With this approach, you will face a Massive View Controller after few minutes of coding, even if your module just fetches data from the API and displays it.

To avoid it, you need to follow the separation of concerns principle. And in order to follow it, you need to clearly understand the responsibilities of each module and its submodules.

View Layer

View layer is what the user sees and interacts with. In MVC, it’s a UIViewController and its UIView.

I won’t dive deep into separation of View and View Controller in this article, if you want you can check this article with more detailed information and an example.

The responsibility of a view is to display, style, and customize all subviews. In the View, you create all your subviews, set up their constraints, and configure their properties like font, color, border, shadow, etc.

View Controller is responsible for:

  • Handling user actions and input;
  • Handling lifecycle events;
  • Handling UI notifications;
  • Delegates.

Inside this class you have to set up actions for your view’s buttons, switches, texfields and other UIControl subclasses. Using UIViewController lifecycle methods, you handle lifecycle events. And you provide delegates for UITableView, UICollectionView and other UI components requiring delegates.

Short code example:

class SomeView: UIView {
// All subviews
// Subviews layout
// Subviews styling and configuration
}

class SomeViewController: UIViewController {

override func loadView() {
self.view = SomeView()
}

//Lifecycle
//Actions
}

//Delegates
extension SomeViewController: UITableViewDataSource {

}

In other words

View is a container for all subviews. View Controller handles user’s interactions.

Controller Layer

Controller is responsible for handling business logic and interacting with a Model layer. Since View Controller is a View and cannot be used as a Controller, you need to create a separate class for it.

Since View needs to interact with a Controller and send user’s actions and inputs to it, View must have a link to the Controller. You need to add a link to the Controller inside your View Controller class. With that link, View Controller is able to inform Controller about user’s actions.

Inside Controller, you put all your external services and data providers. But even with this approach, you may end up with a Massive Controller issue. If your Controller’s code grows fast, you can move some pieces of logic from the Controller into another external service or manager.

Here is a short example. You are working on a taxi app. At one screen, you need to build a route for the trip and calculate its price. Inside your Controller, you are fetching data about waypoints, prices and available cabs. You can create a new service, let’s say RouteBuilderService. Inside RouteBuilderService you will fetch all data you need, do all requires calculations and return a struct containing all data about the route.

Short code example:

class SomeController {
let someService: SomeService1
let anotherService: SomeService2
// Another services

func submitAction() { }
// Another actions
}

class SomeViewController: UIViewController {

let controller: SomeController

@objc func sumbitButtonPressed() {
controller.submitAction()
}
}

Model Layer

Model layer is responsible for all data manipulation in your module. It may contain API services, Database services, Data Validation services, etc.

But here is the thing, API and Database service are just data providers, providing us with a raw data fetched from the API or Database. Mostly, Controller fetches data using external services and then format this data depending on its needs. To reduce Controller’s load, you may create a Repository class.

Repository usually contain one or more data providers inside. It performs all data manipulations and provides the Controller with a formatted data. If your logic requires fetching data, caching it inside the database and returning it to the Controller, Repository is the best place to do it.

Short code example:

class SomeRepository {
let apiService: APIService
let databaseService: DatabaseService
// Other data providers

func fetchSomeData() async -> SomeData {
let rawData = await apiService.fetchSomeRawData()
let formattedData = formatSomeRawData(rawData)
databaseService.cacheSomeData(formattedData)
return formattedData
}

func formatSomeRawData(_ data: SomeRawData) -> SomeData {
// Formatting
return formattedData
}
}

class SomeController {
let someRepository: SomeRepository

func someAction() async {
let someData = repository.fetchSomeData()
// Some business logic
}
}

And that’s it?

Yes, that’s mostly it. Layers that I described follow the separation of concerns principle, easy to update and maintain. You won’t break any business logic if you need to do UI changes, and vice versa. Repository class allows you to update the Model layer with ease, without any worries that you can break something in a Controller.

I hope I was able to show you that MVC isn’t bad architecture, and maybe you will give it a try in your next project. Thanks for your time, in my next articles I will show how to use another MV patterns.

Check out my other articles about iOS Development

https://medium.com/@artem.khalilyaev

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job

--

--