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 tutorial you’ll learn:
Let’s get to it!
The official Apple documentation defines delegation as:
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:
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.
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:
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:
delegate
property, of type BakeryDelegate
, has been addedcookieWasBaked(_:)
is called on the delegate in makeCookie()
, with the delegate?.cookieWasBaked(cookie)
codeThat’s not all. Check this out:
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.delegate
property is an optional, and we use optional chaining when calling that cookieWasBaked(_:)
function. When delegate
is nil
, the function isn’t called.Need a refresher on protocols? Check out this tutorial: Protocols in Swift Explained.
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:
CookieShop
object and assign it to the shop
constant.Bakery
object and assign it to the bakery
constant.shop
to bakery.delegate
. This makes the shop
the delegate of the bakery
.bakery
makes a cookie, that cookie is handed off to the shop
, that can sell it to a happy customerAnd 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 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:
UITableViewDelegate
and UITableViewDataSource
protocols to manage table view interaction, displaying cells, and changing the table view layoutCLLocationManager
uses the CLLocationManagerDelegate
to report location-related data to your app, such as an iPhone’s GPS coordinatesUITextView
uses the UITextViewDelegate
to report about changes in a text view, such as inserted new characters, selection changes, and when text editing stopsWhen 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:
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.
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:
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.
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 selectedlocationManager(_:didUpdateLocations:)
in CLLocationManagerDelegate
tells the delegate that location updates have come innavigationController(_:willShow:animated:)
in UINavigationControllerDelegate
tells the delegate that a navigation controller is about to display a view controllerWhy don’t you give the example code from this tutorial a try? Use the Swift Sandbox below to play around with delegation
[sandbox]
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()
[/sandbox]
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:
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.
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.
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.
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:
So, to summarize:
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.
Want to learn more? Check out these resources: