Delegation In Swift Explained

Written by Reinder de Vries on September 1 2018 in App Development

Delegation In Swift Explained

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 article 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

Let’s get to it!

  1. What Is Delegation?
  2. A Simple Example In Swift
  3. Delegation In Practical iOS Development
  4. Delegation: An Example With UITextView
  5. Delegation: Passing Data Back
  6. Why Use Delegation?
  7. Further Reading

What Is Delegation?

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:

  • 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 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.

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.

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

  • In the bakery’s shop
  • On the bakery’s website
  • Wholesale, to cookie distributors

Selling cookies isn’t the bakery’s responsibility, but delivering 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()

And 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. Note: If you’re unfamiliar with protocols, read this tutorial about them.
  • The delegate property is an optional, and we use optional chaining when calling that cookieWasBaked(_:) function. The code still runs OK when delegate is nil, and there is no delegate! Cookies are baked, just nothing is done with them.

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.

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

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 shows you:

  • 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: none of these events originate from the developer! 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.

Delegation: 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. (Assume this textView is initialized properly in 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.

Delegation: Passing Data Back

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 use 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 “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

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, it’s a great way to “hook into” events that happen within code you don’t control.

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 unlikely 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.

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

The one 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.

This has the same advantages of 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.

Oh, and 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.

Learn how to build iOS apps

Get started with iOS 12 and Swift 5

Sign up for our iOS development course Zero to App Store and learn how to build professional iOS 12 apps with Swift 5 and Xcode 10.

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.

Want to learn more? Check out these resources:

Reinder de Vries

Reinder de Vries

Reinder de Vries is a professional iOS developer. He teaches app developers how to build their own apps at LearnAppMaking.com. Since 2009 he has developed a few dozen apps for iOS, worked for global brands and lead development at several startups. When he’s not coding, he enjoys strong espresso and traveling.