The delegation pattern is among the most common patterns in iOS and OS X development. It is a simple pattern that is heavily used by Apple's frameworks and even the simplest iOS application leverages delegation to do its work. Let's start by looking at the definition of delegation.
1. What Is Delegation?
Definition
The definition of the delegation pattern is short and simple. This is how Apple defines the pattern.
A delegate is an object that acts on behalf of, or in coordination with, another object when that object encounters an event in a program.
Let's break that down. The delegation pattern involves two objects, the delegate and the delegating object. The UITableView
class, for example, defines a delegate
property to which it delegates events. The delegate property needs to conform to the UITableViewDelegate
protocol, which is defined in the header file of the UITableView
class.
In this example, the table view instance is the delegating object. The delegate is usually a view controller, but it can be any object that conforms to the UITableViewDelegate
protocol. If you're unfamiliar with protocols, a class conforms to a protocol if it implements the required methods of the protocol. We'll look at an example a bit later.
When the user taps a row in the table view, the table view notifies its delegate by sending it a message of tableView(_:didSelectRowAtIndexPath:)
. The first argument of this method is the table view sending the message. The second argument is the index path of the row the user tapped.
The table view only notifies its delegate of this event. It is up to the delegate to decide what needs to happen when such an event occurred. This separation of responsibilities, as you'll learn in a moment, is one of the key benefits of the delegation pattern.
Advantages
Reusability
Delegation has several advantages, the first one being reusability. Because the table view delegates user interaction to its delegate, the table view doesn't need to know what needs to happen when one of its rows is tapped.
Put differently, the table view can remain ignorant of the implementation details of how user interaction is handled by the application. This responsibility is delegated to the delegate, a view controller for example.
The direct benefit is that the UITableView
class can be used as is in most situations. Most of the times, there's no need to subclass UITableView
to adapt it to your application's needs.
Loose Coupling
Another important advantage of delegation is loose coupling. In my article about singletons, I emphasize that tight coupling should be avoided as much as possible. Delegation is a design pattern that actively promotes loose coupling. What do I mean by that?
The UITableView
class is coupled to its delegate to do its work. If no delegate is associated with the table view, the table view cannot handle or respond to user interaction. This means that there needs to be a certain level of coupling. The table view and its delegate, however, are loosely coupled, because every class that implements the UITableViewDelegate
protocol can act as the table view's delegate. The result is a flexible and loosely coupled object graph.
Separation of Responsibilities
A lesser known advantage of delegation is separation of responsibilities. Whenever you create an object graph, it is important to know which objects are responsible for which tasks. The delegation pattern makes this very clear.
In the case of the UITableView
class, the delegate of the table view is responsible for handling user interaction. The table view itself is responsible for detecting user interaction. This is a clear separation of responsibilities. Such a separation makes your job as a developer much easier and clearer.
2. Example
There are a few flavors of the delegation pattern. Let's continue by further exploring the UITableViewDelegate
protocol.
Delegation
The UITableViewDelegate
protocol needs to be implemented by the table view's delegate. The table view notifies its delegate through the UITableViewDelegate
protocol about user interaction, but it also uses the delegate for its layout.
An important difference between Swift and Objective-C is the possibility to mark protocol methods as optional. In Objective-C, the methods of a protocol are required by default. The methods of the UITableViewDelegate
protocol, however, are optional. In other words, it is possible for a class to conform to the UITableViewDelegate
protocol without implementing any of the protocol's methods.
In Swift, however, a class conforming to a particular protocol is required to implement every method defined by the protocol. This is much safer since the delegating object doesn't need to verify whether the delegate implements a protocol method. This subtle, but important, difference is illustrated later in this tutorial when we implement the delegation pattern.
Data Source
There is another pattern that is closely related to the delegation pattern, the data source pattern. The UITableViewDataSource
protocol is an example of this pattern. The UITableView
class exposes a dataSource
property that is of type UITableViewDataSource
(id<UITableViewDataSource>
in Objective-C). This means that the table view's data source can be any object that implements the UITableViewDataSource
protocol.
The data source object is responsible for managing the data source of the object it is the data source of. It's important to note that the data source object is responsible for keeping a reference to the items it exposes to the target object, such as a table view or collection view.
A table view, for example, asks its data source for the data it needs to display. The table view is not responsible for keeping a hold of the data objects it needs to display. That role is handed to the data source object.
The data source pattern fits nicely in the Model-View-Controller or MVC pattern. Why is that? A table view, for example, is part of the view layer. It doesn't and shouldn't know about the model layer and isn't in charge of handling the data that is coming from the model layer. This implies that the data source of a table view, or any other view component that implements the data source pattern, is often a controller of some sort. On iOS, it's usually a UIViewController
subclass.
The method signatures of a data source protocol follow the same pattern as those of a delegate protocol. The object sending the messages to the data source is passed as the first argument. The data source protocol should only define methods that relate to the data that's being used by the requesting object.
A table view, for example, asks its data source for the number of sections and rows it should display. But it also notifies the data source that a row or section was inserted or deleted. The latter is important since the data source needs to update itself to reflect the changes visible in the table view. If the table view and the data source get out of sync, bad things happen.
3. Implementation
Objective-C
Implementing the delegate pattern is pretty simple now that we understand how it works. Take a look at the following Objective-C example.
#import <UIKit/UIKit.h> @protocol AddItemViewControllerDelegate; @interface AddItemViewController : UIViewController @property (weak, nonatomic) id<AddItemViewControllerDelegate> delegate; @end @protocol AddItemViewControllerDelegate <NSObject> - (void)viewControllerDidCancel:(AddItemViewController *)viewController; - (void)viewController:(AddItemViewController *)viewController didAddItem:(NSString *)item; @optional - (BOOL)viewController:(AddItemViewController *)viewController validateItem:(NSString *)item; @end
We declare a class, AddItemViewController
, which extends UIViewController
. The class declares a property, delegate
, of type id<AddItemViewControllerDelegate>
. Note that the property is marked as weak, which means that an AddItemViewController
instance keeps a weak reference to its delegate.
Also note that I've added a forward protocol declaration below the import statement of the UIKit framework. This is necessary to avoid a compiler warning. We could move the protocol declaration below the import statement, but I prefer to put it below the class interface. This is nothing more than a personal preference.
The protocol declaration is also pretty simple. The AddItemViewControllerDelegate
protocol extends the NSObject
protocol. This isn't mandatory, but it will prove to be very useful. We'll find out why that is a bit later.
The AddItemViewControllerDelegate
protocol declares two required methods and one optional method. As I mentioned earlier, it's a good practice to pass the delegating object as the first parameter of every delegate method to inform the delegate which object is sending the message.
The required methods notify the delegate about an event, a cancelation or an addition. The optional method asks the delegate for feedback. It expects the delegate to return YES
or NO
.
This is the first piece of the delegation puzzle. We've declared a class that declares a delegate
property and we've declared a delegate protocol. The second piece of the puzzle is invoking the delegate methods in the AddItemViewController
class. Let's see how that works.
In the implementation of the AddItemViewController
class, we implement a cancel:
action. This action could be hooked up to a button in the user interface. If the user taps the button, the delegate is notified of this event and, as a result, the delegate could dismiss the AddItemViewController
instance.
- (IBAction)cancel:(id)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(viewControllerDidCancel:)]) { [self.delegate viewControllerDidCancel:self]; } }
It is recommended to verify that the delegate object isn't nil
and that it implements the delegate method we're about to invoke, viewControllerDidCancel:
. This is easy thanks to the respondsToSelector:
method, declared in the NSObject
protocol. This is the reason why the AddItemViewControllerDelegate
protocol extends the NSObject
protocol. By extending the NSObject
protocol, we get this functionality for free.
You can omit the check for the delegate property being nil
, since respondsToSelector:
will return nil
if the delegate property is nil
. I usually add this check since it clearly shows what we're testing.
The third and final piece of the puzzle is the implementation of the delegate protocol by the delegate object. The following code snippet shows the creation of an AddItemViewController
instance and the implementation of one of the delegate methods.
- (IBAction)addItem:(id)sender { // Initialize View Controller AddItemViewController *viewController = [[AddItemViewController alloc] init]; // Configure View Controller [viewController setDelegate:self]; // Present View Controller [self presentViewController:viewController animated:YES completion:nil]; }
- (void)viewControllerDidCancel:(AddItemViewController *)viewController { // Dismiss Add Item View Controller ... }
Don't forget to conform the class that acts as the delegate to the AddItemViewControllerDelegate
protocol as shown below. You can add this in the class interface or in a private class extension.
#import "AddItemViewController.h" @interface ViewController () <AddItemViewControllerDelegate> @end
Swift
In Swift, the delegation pattern is just as easy to implement and you'll find that Swift makes delegation slightly more elegant. Let's implement the above example in Swift. This is what the AddItemViewController
class looks like in Swift.
import UIKit protocol AddItemViewControllerDelegate: NSObjectProtocol { func viewControllerDidCancel(viewController: AddItemViewController) func viewController(viewController: AddItemViewController, didAddItem: String) func viewController(viewController: AddItemViewController, validateItem: String) -> Bool } class AddItemViewController: UIViewController { var delegate: AddItemViewControllerDelegate? func cancel(sender: AnyObject) { delegate?.viewControllerDidCancel(self) } }
The protocol declaration looks a bit different in Swift. Note that the AddItemViewControllerDelegate
protocol extends the NSObjectProtocol
instead of the NSObject
protocol. In Swift, classes and protocols cannot have the same name, which is why the NSObject
protocol is named differently in Swift.
The delegate
property is a variable of type AddItemViewControllerDelegate?
. Note the question mark at the end of the protocol name. The delegate property is an optional.
In the cancel(_:)
method, we invoke the viewControllerDidCancel(_:)
delegate method. That single line shows how elegant Swift can be. We safely unwrap the delegate
property before invoking the delegate method. There's no need to check if the delegate implements the viewControllerDidCancel(_:)
method since every method of a protocol is required in Swift.
Let's now look at the ViewController
class, which implements the AddItemViewControllerDelegate
protocol. The interface shows us that the ViewController
class extends the UIViewController
class and adopts the AddItemViewControllerDelegate
protocol.
import UIKit class ViewController: UIViewController, AddItemViewControllerDelegate { func addItem(send: AnyObject) { // Initialize View Controller let viewController = AddItemViewController() // Configure View Controller viewController.delegate = self // Present View Controller presentViewController(viewController, animated: true, completion: nil) } func viewControllerDidCancel(viewController: AddItemViewController) { // Dismiss Add Item View Controller ... } func viewController(viewController: AddItemViewController, didAddItem: String) { } func viewController(viewController: AddItemViewController, validateItem: String) -> Bool { } }
In the addItem(_:)
method, we initialize an instance of the AddItemViewController
class, set its delegate
property, and present it to the user. Note that we've implemented every delegate method of the AddItemViewControllerDelegate
protocol. If we don't, the compiler will tell us that the ViewController
class doesn't conform to the AddItemViewControllerDelegate
protocol. Try this out by commenting out one of the delegate methods.
Conclusion
Delegation is a pattern you'll come across frequently when developing iOS and OS X applications. Cocoa relies heavily on this design pattern so it's important to become familiar with it.
Since the introduction of blocks, a few years ago, Apple has slowly offered an alternative blocks-based API to some delegation implementations. Some developers have followed Apple's lead by offering their own blocks-based alternatives. The popular AFNetworking library, for example, relies heavily on blocks instead of delegation, resulting in an elegant, intuitive API.
Comments