The author presents a new approach to implementing the MVC architecture in iOS apps, addressing common issues such as massive view controllers, vague separation of responsibilities, and tight coupling between controller and view.
Abstract
In this article, the author introduces a new way of implementing the MVC architecture in iOS apps to address common issues faced by developers. The author suggests separating responsibilities between the View, Controller, and Model components, with each performing its unique tasks and sending messages to other components for further processing. The author provides an example project demonstrating the use of this architecture, which includes a simple app that allows adding items to a list, displaying them in a UITableView, and performing Create, Read, and Delete operations using the Realm Database. The article also explains the project structure, which includes three folders: App, Modules, and Supporting Files. Each module consists of a Contract file containing four protocols, a View folder with UI elements, a Controller folder with a UIViewController subclass, and a Model folder with simple objects responsible for performing operations with them.
Bullet points
The author presents a new approach to implementing the MVC architecture in iOS apps.
The new approach aims to address common issues faced by developers, such as massive view controllers, vague separation of responsibilities, and tight coupling between controller and view.
The author suggests separating responsibilities between the View, Controller, and Model components.
The View performs layout of its subviews, waits for the Controller's response with fresh data, and refreshes itself with that data.
The Controller responds to UI events by firing relevant methods in the Model, receiving the result from the Model, transforming it into a UI-displayable format, and sending it back to the View.
The Model performs operations with objects and sends the raw result to the Controller.
The author provides an example project demonstrating the use of this architecture, which includes a simple app that allows adding items to a list, displaying them in a UITableView, and performing Create, Read, and Delete operations using the Realm Database.
The project structure includes three folders: App, Modules, and Supporting Files.
Each module consists of a Contract file containing four protocols, a View folder with UI elements, a Controller folder with a UIViewController subclass, and a Model folder with simple objects responsible for performing operations with them.
The author explains how each component interacts with the others and provides code snippets to illustrate the implementation.
New MVC: Single Responsibility Principle and Delegation
A different way of implementing MVC in your iOS apps
Last weekend, I was tinkering with the idea of a correct MVC architecture implementation in iOS apps. Having heard many developers complaining about Apple’s suggested design pattern, I decided to come up with my own solution, trying to address these common issues:
The notorious “Massive View Controller.”
Vague separation of responsibilities.
Tight coupling between controller and a view.
A dumb and plain model that does almost nothing.
Having had hands-on experience with MVP, MVVM, and VIPER, I could see a way to improve an MVC design pattern, keeping the nuances of these architectures in mind.
Let’s Start
Let’s take a look at this diagram:
This is how the ideal MVC should be implemented. We can clearly see that each component does its own unique task, then sends a message to another one to do a different task, and the cycle repeats. Overall, it represents a bidirectional data flow:
View sends a user action to a Controller.
Controller tells a Model to perform a certain task based on that user action.
Model forwards the result of the task back to the Controller.
Controller sends the displayable result back to the View , which refreshes itself with fresh data.
Example Project
The sample project is a simple app that allows you to add items to the list, display them in a UITableView, and perform Create, Read, and Delete operations with them using the Realm Database.
The source code of the app is available on GitHub.
Project Structure
The root of the project is divided into three folders: App, Modules, and Supporting Files. App contains the AppDelegate.swift file, while Supporting Files folder has Assets.xcassets, LaunchScreen.storyboard, and an Info.plist. Our main focus here is the Modules folder.
This is what each module is comprised of:
Contract has a file containing four protocols that View, Controller, and Model conform to. Each protocol defines the responsibilities of Model, View, and Controller.
View contains all our UI elements that subclass from UIView . All it does is performing layout of its subviews, waiting for the Controller’s response with fresh data, then refreshing itself with that data.
Controller contains a UIViewController subclass that’s a mediator between View and Model. Its responsibility is to respond to UI events by firing relevant methods in the Model, receiving the result from the Model, then transforming it into a UI-displayable format and sending it back to the View.
Model contains simple objects and is responsible for performing operations with them. It doesn’t assume anything about how these objects will be transformed to be layout in the view. After it does a needed operation, like fetching from a database, it sends the raw result to the Controller.
With that clear, let’s explore the architecture in practice.
ItemsContract
Let’s define four protocols that describe all the rules that Model, View, and Controller will follow:
ItemsContract.swift
Here’s an elaborate description of each protocol:
ItemsViewInput contains methods that the ItemsView will fire in response to user actions or its lifecycle events. ItemsViewController will conform to this protocol.
ItemsControllerInput has methods that will be run in response to ItemsViewInput. For example, when a View loads, it fires the onViewLayout() method, which leads to Controller triggering retrieveItems() method. ItemsModel will conform to this protocol.
ItemsModelOutput is comprised of methods, which will be fired when the ItemsModel completes its task. For instance, when retrieveItems() method was run and items were fetched, it runs the onItemsRetrieval(_ items: Results<Item>) method which sends the raw result back to the ItemsViewController . ItemsViewController will conform to this protocol.
ItemsControllerOutput has methods that will be called when the ItemsViewController receives the result from ItemsModel and transforms it into a suitable format for the ItemsView to display. ItemsView will conform to this protocol.
Note that Controller has strong references to the View and Model, while both View and Model have a weak controller property. This way we avoid potential memory leaks.
AppDelegate
Let’s create and set up the rootViewController for the window:
ItemsView
We create all our UI elements here.
ItemsView.swift
Here’s how we send a message to the controller when the view was layout:
Let’s take a closer look at the ItemsControllerOutput protocol conformance:
As I wrote before, these methods will be fired inside the ItemsViewController.swift file when the ItemsModel sends a particular result to it.
ItemsViewController
Here we should conform to two protocols: ItemsViewInput and ItemsModelOutput. We will receive user actions through the ItemsViewInput, call needed methods in the ItemsModel, then receive the result through the ItemsModelOutput.
ItemsViewController.swift
First, we assign the controller’s view property to the ItemsView:
Now, let’s explore how we handle user actions:
As you can see, when a user taps on the “Add” bar button, ItemsView fires the onAddTap method which signals the ItemsViewController to present an alert. Or, when the “Save” button is tapped on the alert, ItemsViewController tells the ItemsModel to save an item with a title in the Realm database.
And this is how we work with Model’s operation result:
onItemsRetrieval method is an example of result transformation and forwarding it to the view. We receive a list of Item objects from the ItemsModel, transform it into an array of String and send this array to the ItemsView.
onUUIDRetrieval method is also interesting. Here, we don’t want the ItemsView to do anything. All we care about is navigating to the new screen that displays the selected item’s title. We simply build up a new module, and push the ItemDetailViewController onto the navigation stack.
ItemsModel
We need to create an Item object here and provide methods for working with it.
Item.swift
Represents a simple Realm object:
ItemModel.swift
This is where all the interaction with Item will happen:
We can see that ItemsModel is responsible for fetching, adding, and deleting items. On completion of each task, it invokes a relevant method inside the ItemsViewController:
As a result, we have the following workflow:
ItemsView loads and the layoutSubviews() method is triggered.
ItemsModel retrieves items from the Realm database then calls controller?.onItemsRetrieval() passing in the items property as a parameter.
ItemsViewController transforms items into an array of String , then calls itemsView?.onItemsRetrieval() passing in the titles array as a parameter.
ItemsView reacts to the new data inside the onItemsRetrieval() method by assigning titles property and reloading the tableView.
With that, we’ve completed the Items module.
ItemDetail
The implementation here is very similar.
ItemDetailContract.swift
As before, we define four protocol for our ItemDetailView, ItemDetailViewController, and ItemDetailModel to conform to:
ItemDetailView.swift
Similar to how we did it in the ItemsView , here the ItemDetailView waits for the ItemsDetailViewController’ss response using the onItemRetrieval method:
ItemDetailViewController.swift
Waits for the ItemDetailView input and ItemDetailModel output:
ItemDetailModel.swift
Model receives the command from the Controller, performs an item fetching, then forwards the result back to the Controller:
Wrapping Up
As a result, we have separated responsibilities inside our app between three components:
View sends user actions and updates itself on the Controller’s response.
Controller receives the View input and tells Model what to do based on that input, afterward receiving the Model response, transforming it into a View-suitable format and sending it back to the View .
Model performs a needed business logic task, sending the raw result back to the Controller .
Note that to set up a new module, we had to repeat some steps. For that purpose, I created an Xcode File template that generates an MVC module. Instructions on how to add it to Xcode are described in the README.md file of this repo: