Using the Model-View-Controller Architecture

Gavin Shrader
Aug 5, 2018 · 4 min read
Basic layout of MVC pattern

This article was written using Swift 5.

When creating iOS applications, I often run into the need for communication between my Model, View and Controller classes. The picture above demonstrates the architectural pattern known as “MVC”, a method for designing the data flow in your projects.

A benefit to using an architectural pattern is that your code will be easy to understand, modular and maintainable. By breaking down your classes into each category you will need an efficient method of communicating from model -> controller -> view and vice-versa. I will highlight 3 of the methods I commonly use to achieve this communication.


  1. Using Notifications
  2. Using Delegation
  3. Using Callbacks

After following this tutorial hopefully you will get a basic understanding of the pros/cons of each method and be able to pick the solution that works best for you!


Notifications

Using notifications works well when you have a lot of observers that need to listen for an update. For instance, if you have 5 classes that all load saved data then you can alert all 5 classes to a successful network request at once.

The basic syntax:

Note: The “@objc” tag is required in front of any function that is called through the #selector.

Notifications are not optimal for sending data (though it is possible) but, rather, are best used to alert a class that new data is available. Say you have a data model that downloads network data, once this data is saved to memory you alert your view controllers to propagate this newly saved data into view.

Here is an example of a working notification structure:

Delegation

Delegation is the method I use most often for communicating between my Model, View and Controller classes. The delegation design pattern uses a protocol to define methods, properties and the other requirements necessary for a class to implement. I like to think of a protocol as a contract — by fulfilling the contract laid out in a protocol, you are obliged to implement its requirements.

To implement the delegation pattern you must initialize a protocol:

protocol ModelDelegate: class {
func didReceiveData(_ data: String)
}

In the model class (the class that will hold this delegate) you must initialize a weak reference to the ModelDelegate.

The reference to the delegate must be weak or unowned. By using a strong reference you may find yourself creating a retain cycle where the delegate holds a reference to the parent class and the parent class to the delegate. By creating a strong reference cycle, both objects hold references to each other in memory, thus not allowing ARC to deallocate the delegated object upon its de-initialization.

class Model {
weak var delegate: ModelDelegate?
func downloadData() {
let data = "Network request information."
delegate?.didReceiveData(data)
}
}

Now create an instance of Model in its Controller class and assign its delegate to self.

class ViewController: UIViewController {
let model = Model()

override func viewDidLoad() {
super.viewDidLoad()
model.delegate = self
model.downloadData()
}
}
extension ViewController: ModelDelegate {
func didReceiveData(_ data: String) {
print(data)
}
}

In order to assign the model’s delegate to self (self = the ViewController class in this example) the ViewController must conform to the ModelDelegate protocol. We can achieve this by adding an extension of view controller that adopts the protocol in order to conform to the layout defined in ModelDelegate.

In this example, the ViewController holds an instance of a Model. Once the model has successfully downloaded data it will call the function “didReceiveData” inside the ViewController. Once this function has been called you can update your View class within the ViewController.

Callbacks

Callbacks are simple to set up. I don’t often use them as I prefer the rigidity of the delegation pattern. However, they are useful for simple communication.

In a View class we have a function that runs an animation. Upon the completion of the animation we want to alert our ViewController that the animation has finished.

class View {
func runAnimation(completion: @escaping() -> ()) {
//run animation, upon completion send callback
completion()
}
}

In the ViewController create an instance of the View class and call the runAnimation() function:

Take special note of the “[weak self] in” inside the closure on runAnimation(). By specifying that self is a weak reference, we avoid retain cycles. If we capture self strongly then the closure would hold a strong reference to self, thus not allowing deallocation in memory.

It is also possible to send data directly through a callback:

Note: the underscore within a function definition means that we do not need to include the parameter name when calling a method.


Hopefully this article helped you learn more about some of the communication options available in Swift. I often find myself using several of these methods inside a single project, as they each have their use-cases. I use delegates for rigid layouts between classes that do lots of communicating. I use Callbacks for simple functions that need to send a callback upon the completion of an action. I use Notifications when I need to alert multiple classes to a new data download or change. If you need any help feel free to email me at shrader.gavin@gmail.com!

Better Programming

Advice for programmers.

Gavin Shrader

Written by

21 year old app developer, programmer, wanderer and thinker. Check out some of my projects on www.gavinshrader.com

Better Programming

Advice for programmers.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade