DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Tarik Dahic
Tarik Dahic

Posted on • Originally published at tarikdahic.com on

The Holy Grail of UIKit: Delegate Pattern

When I started learning to develop apps for iOS I stumbled upon the delegate pattern for the first time. Adopting UITableViewDataSource and UITableViewDelegate protocols felt very strange and confusing.

Why am I doing this? Is this really how to do these things? I asked myself.

After a couple of weeks of learning and reading materials, I clicked and really started to enjoy using this pattern.

What is it?

According to Apple:

Delegation is a simple and powerful pattern in which one object in a program acts on behalf of, or in coordination with, another object. The delegating object keeps a reference to the other objectβ€”the delegateβ€”and at the appropriate time sends a message to it. The message informs the delegate of an event that the delegating object is about to handle or has just handled. The delegate may respond to the message by updating the appearance or state of itself or other objects in the application, and in some cases, it can return a value that affects how an impending event is handled. The main value of delegation is that it allows you to easily customize the behavior of several objects in one central object.

This is a very good definition. But let’s take it step by step by following the illustration below.

  1. Someone creates a Worker. A Worker is someone who does the work and then notifies us when the work is completed or when something is needed to continue the work.
  2. We tell the worker: β€œThis is your delegate. Ask it for something or notify it when you are done.”. That depends on the language that is known between the worker and its delegate. This is always known upfront.
  3. The Worker starts to work.
  4. While the Worker is working it needs some data to continue its work. The worker asks its delegate β€œHow should I do this or that?” and the delegate knowing what the worker needs provides it. When the Worker finishes the work it can tell its delegate β€œI finished the work and here are the results!”.

This is how the delegate pattern works. The communication flow is always 1-to-1 between the delegate and the worker.

The worker is always doing the same job, but by providing different data and reacting differently on work completions we can reuse the worker in a lot of places where we need to handle the different cases.

Delegation works great when there is a clear relationship between the owner and the β€œWorker”. For other cases, you will want to resort to other patterns.

I would also like to repeat that the worker knows what to send or request from the delegate.

Translating this into code

After we’ve covered the basics with the theory we will try to translate this into code. I will be using Swift to implement this pattern.

Define the protocol

We should always start with the delegate protocol, the interface or the language that will the delegate adopt and that the worker will know in advance.

protocol WorkerDelegate: AnyObject {
    func didFinishWork()
}
Enter fullscreen mode Exit fullscreen mode

I declared WorkerDelegate protocol that has one method for this example and that is didFinishWork(). When the worker completes the work it will use the delegate that has conformance to WorkerDelegate protocol and will call the didFinishWork() method on it.

AnyObject

You can notice that the WorkerDelegate protocol conforms to another one and that is AnyObject. We use AnyObject to limit that only class types can inherit our delegate protocol. Only class objects can be weak because they are passed by reference and we want weak delegates so that we don’t create retain cycles while using delegates.

Define the worker

class Worker {
    weak var delegate: WorkerDelegate?

    func startWork() {
        // Start some work..
        //
        //
        // .. and when it is completed, notify the delegate:
        delegate?.didFinishWork()
    }
}
Enter fullscreen mode Exit fullscreen mode

The Worker class has a property delegate that is of type WorkerDelegate. It has a weak reference to something that will be stored in this variable and that is, as I mentioned above to avoid creating potential retain cycles. The delegate is also optional so by default it is nil. This is useful in some cases where setting the delegate is not mandatory.

The Worker has a startWork() method that will start some work and when the work is completed it will notify its delegate by invoking the didFinishWork() method if the delegate is set.

Create the worker and adopt the WorkerDelegate

class ViewController: UIViewController {

    private let worker = Worker()

    override func viewDidLoad() {
        super.viewDidLoad()

        // It is always important to set the delegate before we invoke the worker to start doing something.
        // Better alternative could be to pass the delegate in the initializer of the Worker.
        worker.delegate = self

        worker.startWork()
    }
}

extension ViewController: WorkerDelegate {

    func didFinishWork() {
        // Worker finished the work. Now we can react to it by fetching some data or updating the UI.
    }
}
Enter fullscreen mode Exit fullscreen mode

The ViewController has a worker property. In the viewDidLoad() lifecycle method of the ViewController, we set the delegate of the worker to self, so that when the worker is done it will notify the ViewController instance. After setting the delegate, we invoke the startWork() method of the worker and we wait for it to complete to notify us via the didFinishWork() method that we conformed to via WorkerDelegate.

I always like to conform to different protocols in the extensions of the classes because it improves the organisation and the readability of the code.

Examples from UIKit

While working with UIKit we use delegates on daily basis so this is why it is very important to understand this pattern. A good tip for you is when you need to conform to some delegate protocol is to open the protocol definition and see what it has to offer.

Here are a couple of examples:

UITableViewDataSource

We use UITableViewDataSource to provide the UITableView with data. A data source object responds to data-related requests from the table. The minimum for every UITableView that we need to do is to provide it with the numbers of rows per section and to provide UITableViewCell for every row. We do that by implementing methods from the UITableViewDataSource protocol:

// Return the number of rows for the table. We'll assume that the table has only one section.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   return items.count
}

// Provide a cell object for each row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   // Fetch a cell of the appropriate type
   let cell = tableView.dequeueReusableCell(withIdentifier: "cellTypeIdentifier", for: indexPath)

   // Configure the cell using items[indexPath.row]

   return cell
}
Enter fullscreen mode Exit fullscreen mode

In this example we can see how the UITableView asks us (the data source delegate) for data that it will display.

UITableViewDelegate

With UITableViewDelegate we can react to some interactions with the UITableView. In the example below we can override the didSelectRowAt delegate method so that we get notified when user presses the UITableView row.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    // React to the selection of the cell at the indexPath
}
Enter fullscreen mode Exit fullscreen mode

UITableViewDelegate can also be used for more advanced UITableView customization.

MFMailComposeViewControllerDelegate

If we want to use native system UI and configuration to send an email we can use MFMailComposeViewController. We will create the instance of MFMailComposeViewController, configure it, adopts its delegate and send the email. The email completion will be delegated to us in the delegate method below that we implemented by overriding MFMailComposeViewControllerDelegate:

func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
  // React to mail sending completion
}
Enter fullscreen mode Exit fullscreen mode

From here we can see if the email sending was successful or not and react according to that information.

UIImagePickerControllerDelegate

If you want to use some images or videos from the Photos app you possibly implemented UIImagePickerController and conformed to its protocol so that you know when the user picked the media files.

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
  // Use picked images or videos
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

The delegate pattern is very popular in the iOS world and it is very present in UIKit. I like this pattern and I use it in my work but it has some limitations. The communication is always 1-to-1 and that is not practical in some cases so we need to resort to something like an Observer pattern. The amount of code that needs to be added is not small if we are developing something trivial and the testing is harder.

Alternative to using delegates can be passing functions to β€œWorkers” and invoking them when we need something or when we want to notify someone. This can result in fewer lines of code but can complicate things if we overuse it. We would also need to be careful not to create retain cycles with this approach.

I hope that this article helped you to understand or to strengthen your knowledge with this pattern.

Top comments (0)

Welcome!

πŸ‘‹ Have You Posted on DEV Yet?

Head over to our Welcome Thread and tell us a bit about yourself!