iOS

Design Patterns in iOS: Delegation

Design patterns are great tools to solve reoccurring problems in software design. They are blueprints of solutions you can apply.

One of the most important design patterns on iOS is the delegate pattern. It is the design pattern most commonly used in many Apple frameworks and libraries. Being familiar with this pattern will:

  • Add a tool to solve reoccurring software problems in your code
  • Quickly navigate and use Apple APIs

Additionally to the above other iOS developers are likely to be familiar with this pattern and thus easily get acquainted with your code.

In this post I will help you understand the delegate pattern. Then we’ll look when to use the delegate pattern. Finally we’ll look at an example from Apple’s UIKit framework.

For this post I assume you are familiar with the basics of Swift and iOS development.

What is the delegate pattern?

Before looking at the delegate pattern as whole let’s break it down. What is delegate?

to give a particular job, duty, right, etc. to someone else so that they do it for you

- Cambridge dictionary

Simply instead of you doing the work you delegate the work to someone else. What does this look like in software? Let’s look an example. Let’s say you are ordering pizza. You might want to eat this pizza yourself or send it to a friend to cheer them after a hard week (what a great friend you are!). There are 2 scenarios here. You either delegate the work to eat pizza to yourself (work really?) or you delegate the work of eating pizza to your friend.

Let’s translate this to code. Let’s say Robert wants to buy Mary a pizza for helping him solve a problem he was facing whilst developing his iOS app. For Robert to buy pizza we first need a pizza maker.

class PizzaMaker {    func makePizza() { 

}
}

However in order to make the pizza the pizza maker needs someone to deliver it to. It needs a delegate. Let’s change the code above to capture the new requirements:

protocol PizzaMakerDelegate: AnyObject {
func receivePizza(_ pizza: Pizza)
}
class PizzaMaker { func makePizza(delegate: PizzaMakerDelegate) {
...
}
}

Now in order for Robert to order a pizza for Mary he must set Mary as the delegate of the makePizza function.

class Person: PizzaMakerDelegate { 
let name: String
init(name: String) {
self.name = name
}
func receivePizza(_ pizza: Pizza) {
...
}
}
let mary = Person(name: "Mary")
PizzaMaker().makePizza(delegate: mary)

And that’s the delegate pattern in simple terms!

In the example above we make use of protocols. Protocols establishes a contract of what the delegate must do. If the delegate does not abide to the contract then your code won’t compile.

When to use the delegate pattern

Design patterns are used to solve specific reoccurring problems. The delegate pattern solves the following:

  • Setting a contract that callback instance must conform to
  • Not forcing the caller to deal with the results
  • Ability to callback functions at a later time (asynchronously)

The first point we covered in the previous section. Let’s expand the last two points using the ordering pizza example from the previous section.

In the prior example the PizzaMaker could take some time before delivering the pizza. The PizzaMaker will notify when the pizza is ready to de delivered. Thus the caller of makePizza does not have to hang until the pizza is made.

What if Robert went to the Pizza restaurant and waited until the Pizza was made? Here is how the example would look like:

class PizzaMaker {    func makePizza() -> Pizza {
// Robert will be unavailable for any requests until the Pizza is made
// Robert will then need to deliver the pizza to Mary
}
}
let pizza = PizzaMaker().makePizza()
// deliver pizza to Mary

Robert would be unavailable during the pizza making process. Thus Robert wouldn’t be able to do other tasks whilst he waits. This is synchronous.

In the example above every person who wanted to gift a pizza to another person must wait in the restaurant until the pizza is made and then personally deliver the pizza.

With the delegate pattern you can easily store the reference to the delegate, pass the reference and call it at a later date(asynchronous):

class PizzaMake {    func makePizza(delegate: PizzaMakerdelegate) {
self.prepare(onCompletion: { pizza in
self.putPizzaInOven(pizza, onCompletion: { pizza in
let delivery = PizzaDelivery(delegate: delegate)
})
})
}
...
}
class PizzaDelivery {
private weak var delegate: PizzaMakerDelegate?
init(delegate: PizzaMakerDelegate) {
self.delegate = delegate
}
func deliverPizza() {
// after some time:
DispatchQueue.main.async {
self.delegate.receivePizza()
}
}
}

The delegate pattern thus allows us to easily decouple the caller of the PizzaMaker class from the instance to receive the pizza.

Example of Delegate pattern in Apple code

In this section let’s look at an example where the delegate pattern is used within Apple API’s.

UITextField

When using the UIKit framework you can create text field to capture users text input. Let’s you are creating a field where the user must input their email address. When the user finishes editing the text you would like to validate that the text is a valid email address. For such cases UITextField ’s allows you to set a delegate which will be notified when the user finishes editing the text.

Try it out by creating a new UIKit based Single View iOS app and changing the default ViewController:

class ViewController: UIViewController, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.addEmailTextField()
}
private func addEmailTextField() {
let textField = UITextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.layer.borderWidth = 1
textField.layer.borderColor = UIColor.black.cgColor
textField.placeholder = "Enter your email address here your@email.com"
textField.delegate = self
self
.view.addSubview(textField)
NSLayoutConstraint.activate([
textField.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
textField.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
])
}

func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
self.validateTextIsEmail(textField.text ?? "")
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
}
private func validateTextIsEmail(_ text: String) {
// Validate text here
print("email text is \(text)")
}
}

Summary

In this post we learnt:

  • what the delegate pattern is
  • how to apply the delegate pattern
  • when to apply the delegate pattern

Final Notes

In the Apple example of the delegate pattern we saw that the delegate pattern is not always about delegating the work to some other code. However the delegate pattern makes it easy to choose who to delegate the work to based on your needs whether its the caller or another instance.

You can read more in depth on delegate pattern in Swift.org’s Language Guide on Protocols.

For more on iOS development follow me on Twitter or Medium!

Senior iOS Engineer @ Onfido. Writing weekly blogs on iOS and programming. Follow me to stay tuned!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store