Back to blog

Delegation in Swift Explained


Aasif Khan
By Aasif Khan | Last Updated on November 25th, 2023 6:37 am | 6-min read

Delegation, also known as the Delegate pattern, is frequently used in practical iOS development. It’s a must-have in your iOS developer’s toolbox, and today we’re going to figure out how delegation works.

In this app development tutorial you’ll learn:

  • What delegation is, how it works, and why it’s useful
  • How to work with the delegate protocols in the iOS SDKs
  • Alternatives of the Delegation pattern, and their uses

Describe your app idea
and AI will build your App

What is Delegation?

Delegation is a design pattern that enables a class to hand off (or “delegate”) some of its responsibilities to an instance of another class.

That’s quite complex, so let’s break it down…

Think about delegation in the real world. Imagine you and I are part of a team that delivers chocolate cookies to an event. You’re in charge of baking cookies, and you delegate making the cookie dough to me. Once I’m done I give the cookie dough to you, so you can use it to bake the cookies.

A few key points stand out:

  • You’re in charge of making cookies, and you delegate creating cookie dough to me
  • You could say that making cookies is your responsibility, and you’re handing off some of that responsibility to me
  • And it goes two ways: I give you the cookie dough once I’m done with my delegated task

It’s not much different in Swift programming! One class delegates a task to another class, handing off some of its responsibilities. This enables the delegate to customize the base class, as we’ll soon find out.

Delegation: A Simple Example in Swift

Let’s look at an example in Swift. First, we’re defining a Cookie struct. Like this:

struct Cookie {
var size:Int = 5
var hasChocolateChips:Bool = false
}
And then we define a class called Bakery. Like this:

class Bakery
{
func makeCookie()
{
var cookie = Cookie()
cookie.size = 6
cookie.hasChocolateChips = true
}
}
See what happens? The Bakery class has a function called makeCookie() that creates a cookie with the Cookie struct, and sets some of its properties, like size.

A this point we want to sell the cookies in 3 different ways:

  1. In the bakery’s shop
  2. On the bakery’s website
  3. Wholesale to cookie distributors

Selling cookies isn’t the bakery’s responsibility, but producing cookies is. So, we need a way to deliver cookies once they are baked without coding all that into the Bakery class. That’s where delegation comes in!

First, we’re defining a protocol that will encapsulate the responsibilities that we’re handing off. Like this:

protocol BakeryDelegate {
func cookieWasBaked(_ cookie: Cookie)
}
This BakeryDelegate protocol defines one function cookieWasBaked(_:). This delegate function will get called whenever a cookie has been baked.

Second, we’re incorporating delegation into the Bakery class. Like this:

class Bakery
{
var delegate:BakeryDelegate?

func makeCookie()
{
var cookie = Cookie()
cookie.size = 6
cookie.hasChocolateChips = true

delegate?.cookieWasBaked(cookie)
}
}
Two things changed in the Bakery class:

  1. The delegate property, of type BakeryDelegate, has been added
  2. The function cookieWasBaked(_:) is called on the delegate in makeCookie(), with the delegate?.cookieWasBaked(cookie) code

That’s not all. Check this out:

  • The type of the delegate property is the protocol we defined earlier. You can assign any value to the delegate property, as long as it conforms to the BakeryDelegate protocol.
  • The delegate property is an optional, and we use optional chaining when calling that cookieWasBaked(_:) function. When delegate is nil, the function isn’t called.

So, summarizing: you’ve now defined a BakeryDelegate protocol that defines some of the responsibilities that the Bakery delegates, and you’ve implemented the hand-off in makeCookie().

Third, let’s create the actual delegate class! Like this:

class CookieShop: BakeryDelegate
{
func cookieWasBaked(_ cookie: Cookie)
{
print(“Yay! A new cookie was baked, with size \(cookie.size)”)
}
}
The CookieShop adopts the BakeryDelegate protocol, and conforms to that protocol by implementing the cookieWasBaked(_:) function.

And finally, here’s how to put the code together:

let shop = CookieShop()

let bakery = Bakery()
bakery.delegate = shop

bakery.makeCookie()
// Output: Yay! A new cookie was baked, with size 6
Here’s what happens:

  • First, you create a CookieShop object and assign it to the shop constant.
  • Then, you create a Bakery object and assign it to the bakery constant.
  • Then, you assign shop to bakery.delegate. This makes the shop the delegate of the bakery.
  • Finally, when the bakery makes a cookie, that cookie is handed off to the shop, that can sell it to a happy customer

And that’s delegation! The bakery delegates selling cookies to the shop, and hands-off a cookie whenever it makes one.

The power of delegation lies in the simple fact that the bakery doesn’t need to know where its cookies end up. It can provide them to any class that adopts the BakeryDelegate protocol!

The bakery doesn’t need to know about the implementation of that protocol, only that it can call the cookieWasBaked(_:) function when needed.

Why doesn’t the shop call makeCookie() directly whenever it needs a cookie to sell? The answer lies in the nature of delegation, and which class is in control. You’ll learn how in the next section.

Delegation in Practical iOS Development

Delegation is one of the most common design patterns in iOS development. It’s practically impossible to build an iOS app without making use of delegation.

A quick grab of classes in the iOS SDK that use delegation:

  • The UITableView class uses the UITableViewDelegate and UITableViewDataSource protocols to manage table view interaction, displaying cells, and changing the table view layout
  • The CLLocationManager uses the CLLocationManagerDelegate to report location-related data to your app, such as an iPhone’s GPS coordinates
  • The UITextView uses the UITextViewDelegate to report about changes in a text view, such as inserted new characters, selection changes, and when text editing stops

When you look at these three delegate protocols, you quickly see one common pattern: Every one of the events that are delegated are initiated by a class outside your control, the user, the operating system or its hardware.

Let’s have a look:

  • GPS coordinates are provided by the GPS chip, and are delegated to your code whenever the chip has a fix on a GPS position
  • A table view uses a mechanism to display cells on screen, and it needs you to provide these cells when they’re needed according to the table view
  • A text view responds to arbitrary user input, and calls on delegate functions accordingly

Remember the Bakery example from the previous section? In the example, we called makeCookie() ourselves. This would set of a chain of events that leads to the bakery providing a cookie to the shop.

In practical iOS development, the bakery would bake cookies on its own. That’s out of our control, so we need a delegate to respond to these events.

This simple principle shows the need for delegation, because it allows you to hook into events and actions you have no control over.

Imagine you can’t change the code in the Bakery class, just as you can’t change the code in the CLLocationManager class. You can’t tell it to bake a cookie, just like you can’t tell CLLocationManager to get the GPS coordinate of the user. You’ll have to start the cookie baking process, and start the geolocation service, and then wait for data to come in. You hook into this data by making use of delegation.

We’ll look at a real-life example in the next section.

An Example with UITextView

Let’s look at an example. You’re making a simple view controller to take notes. It includes a text view, and that text view uses delegation.

Like this:

class NoteViewController: UIViewController, UITextViewDelegate
{
var textView:UITextView =

func viewDidLoad()
{
textView.delegate = self
}
}
In the above code we’re defining a UIViewController subclass called NoteViewController. It adopts the UITextViewDelegate protocol, and sets up a simple text view with the textView property. You can assume this textView is initialized properly within init().

Within the viewDidLoad() function, you’re assigning self to the delegate property of textView. Said differently, the current instance of NoteViewController is the delegate of the text view.

It’s common practice, but not always recommended, to use a view controller as the delegate of a particular class. Doing this can lead to lengthy view controller classes. I’ve given some alternatives, below.

According to the UITextViewDelegate protocol, we can now implement a number of delegate functions to respond to events taking place in the text view. Some of these functions are:

  • textViewDidBeginEditing(_:)
  • textViewDidEndEditing(_:)
  • textView(_:shouldChangeTextIn:replacementText:)
  • textViewDidChange(_:)

When editing of the text view begins and ends, for example, we can highlight the text view to show the user that editing is taking place. When textViewDidChange(_:) is called, we can update a counter that shows the number of characters in the text view.

What’s interesting, is that the textView(_:shouldChangeTextIn:replacementText:) can provide a return value of type Bool. This delegate function is called before one or more characters are entered into the text view. If you return true, entry is allowed, and if you return false, entry is not allowed. You can use this to:

  • Limit the amount of text that can be entered
  • Replace particular words, phrases or characters with something else
  • Prohibit users from copying text into a text view

This shows that delegate class can provide data back to the class that calls the delegate function (i.e., the text view or cookie bakery), which makes it a two-way street.

Passing Data Back with Delegation

Let’s look at how passing data back from a delegate would work for the BakeryDelegate example from before.

First, we’re adjusting the BakeryDelegate to include another function:

protocol BakeryDelegate {
func cookieWasBaked(_ cookie: Cookie)
func preferredCookieSize() -> Int
}
And the makeCookie() function of the Bakery class changes too:

func makeCookie()
{
var cookie = Cookie()
cookie.size = delegate?.preferredCookieSize() ?? 6
cookie.hasChocolateChips = true

delegate?.cookieWasBaked(cookie)
}
See how the preferredCookieSize() function is called on the delegate? This gives the delegate the opportunity to customize the cookie size. And when delegate is nil, the nil-coalescing operator ?? makes sure the size is set to 6 by default.

Then we change our delegate class to include that new function, like this:

class CookieShop: BakeryDelegate
{
func cookieWasBaked(_ cookie: Cookie)
{
print(“Yay! A new cookie was baked, with size \(cookie.size)”)
}

func preferredCookieSize() -> Int
{
return 12
}
}
Finally, we run the same code as before:

let shop = CookieShop()

let bakery = Bakery()
bakery.delegate = shop

bakery.makeCookie()
// Output: Yay! A new cookie was baked, with size 12
You’ll see that a cookie with size 12 is baked. That’s because the delegate function preferredCookieSize() lets us provide data back to the Bakery object, with a return value.

A function such as preferredCookieSize() is fairly common in some iOS SDKs. The table view delegate protocol, for instance, defines delegate functions that customize the size of table view cells, headers and footers.

Another frequent practice in iOS SDKs is the use of the words “did”, “should” and “will” in delegate function names. They often tell you about the order of operations, and at what point a delegate function is called. Is it before or after an interaction?

Some examples:

  • tableView(_:willSelectRowAt:) in UITableViewDelegate tells the delegate that a table view row is about to be selected
  • locationManager(_:didUpdateLocations:) in CLLocationManagerDelegate tells the delegate that location updates have come in
  • navigationController(_:willShow:animated:) in UINavigationControllerDelegate tells the delegate that a navigation controller is about to display a view controller

Try Out Delegation Yourself!

Why don’t you give the example code from this tutorial a try? Use the Swift Sandbox below to play around with delegation

struct Cookie {
var size:Int = 5
var hasChocolateChips:Bool = false
}

protocol BakeryDelegate {
func cookieWasBaked(_ cookie: Cookie)
func preferredCookieSize() -> Int
}

class Bakery
{
var delegate:BakeryDelegate?

func makeCookie()
{
var cookie = Cookie()
cookie.size = delegate?.preferredCookieSize() ?? 6
cookie.hasChocolateChips = true

delegate?.cookieWasBaked(cookie)
}
}

class CookieShop: BakeryDelegate
{
func cookieWasBaked(_ cookie: Cookie)
{
print(“Yay! A new cookie was baked, with size \(cookie.size)”)
}

func preferredCookieSize() -> Int
{
return 12
}
}

let shop = CookieShop()

let bakery = Bakery()
bakery.delegate = shop

bakery.makeCookie()

Why Use Delegation?

Why use delegation at all? It seems overly complicated, to just pass data back and forth in your code.

A few reasons in favor of delegation:

  • Delegation, and the delegation pattern, is a lightweight approach to hand-off tasks and interactions from one class to another.
  • You only need a protocol to communicate requirements between classes. This greatly reduces coupling between classes.
  • And it separates the responsibilities of the class that generates interactions from the class that responds to these interactions.

In short, delegation is a great way to “hook into” events that happen within code you don’t control, without tightly-coupling your code or decreasing composability.

Delegation vs. Subclassing

An compelling alternative to delegation is subclassing. Instead of using a delegate to get GPS location updates from CLLocationManager, you simply subclass that manager class and respond to location updates directly.

This has a massive disadvantage: you inherit the entire CLLocationManager class, for something as simple as getting a bit of location data. You would need to override some of its default functionality, which you either have to call directly with super or replace entirely.

And lastly, subclassing creates a tightly-coupled class hierarchy, which doesn’t make sense unless your subclass is similar in nature to the class you’re subclassing. This is not likely if you’re merely responding to GPS location updates.

Delegation vs. Notification Center

What about the Observer pattern, as found in NotificationCenter, as an alternative to delegation? It could work: you’d respond to observable changes in a Location object, for instance.

The Observable pattern is useful when your code needs to communicate with multiple components, with a one-to-many or many-to-many relationship. One component in your app broadcasts a signal that multiple other components respond to. And apart from a broadcast type and some associated data, you can’t formalize the requirements for the communication like a protocol can.

Delegation vs. Closures

A viable alternative for the delegation pattern is simply using closures. Instead of calling a delegate function, the delegating class calls a closure that’s defined beforehand as a property on the delegating class.

An increasing number of iOS SDK classes and components are now offering closures as an alternative to delegation and target-action, such as Timer.

Using a closure to hand-off functionality has the same advantages as using delegation (flexible, lightweight, decoupled). Using a closure as a delegate has one main drawback: they’re hard to manage and organize if you use too many of them. Promises or async/await are a helpful approach to manage multiple async closures.

Oh, one last thing before you go… A typical beginner mistake is to make a view controller the delegate of everything. You don’t have to!

Some alternatives:

  • Create a separate delegate class, a controller, that’s responsible for responding to delegate functions
  • Use Swift’s extensions to separate your code, giving each set of delegate functions its own extension
  • Abstract away multiple delegate functions into one controller (or “manager”) and use closures to respond to the fine-grained data you actually need

So, to summarize:

  • Delegation is more lightweight than subclassing, because you don’t have to inherit a complete class or struct
  • Delegation is useful for 1-on-1 relationships, whereas the Observer pattern is more suitable for one-to-many and many-to-many relationships.
  • Delegation is flexible, because it doesn’t require the delegating class to know anything at all about a delegate – only that it conforms to a protocol
  • Closures are a viable alternative to delegation if the base class supports them, provided you keep your closures simple
  • Don’t make the view controller the delegate of everything; use a separate class, or extensions, or a helper class to split up delegation functionality

Further Reading

Delegation will keep its prominent role in the iOS SDKs. Even though it’s quite an old design pattern, it continues to prove its usefulness in practical iOS development.

It’s tricky to grasp, too. Delegation touches on many principles of a good app architecture, and it’s easy to get lost in hooking into this and that function. Hopefully, you now have a clearer perspective on how delegation works, what it’s for, and why you should use it.


Aasif Khan

Head of SEO at Appy Pie

App Builder

Most Popular Posts