If you've ever built anything using UIKit, then you are probably familiar with the delegate pattern. The delegate pattern is used frequently throughout Apple's frameworks and many open source libraries you may come in contact with. But many times, it is treated as a one-size-fits-all solution for problems that it is just not suited for. This post will describe the major shortcomings of the delegate pattern.
Note: This article assumes that you have a working knowledge of the delegate pattern. If you would like to learn more about the delegate pattern, see The Swift Programming Language - Delegation.
Implementation of the delegate pattern can be cumbersome. Most experienced developers will tell you that less code is better code, and the delegate pattern does not really allow for this. To demonstrate, let's try implementing a new view controller that has a delegate using the least amount of lines possible.
First, we have to create a view controller and give it a property for its delegate:
class MyViewController: UIViewController {
var delegate: MyViewControllerDelegate?
}
Then, we define the delegate protocol.
protocol MyViewControllerDelegate {
func foo()
}
Now we have to implement the delegate. Let's make another view controller that presents a MyViewController:
class DelegateViewController: UIViewController {
func presentMyViewController() {
let myViewController = MyViewController()
presentViewController(myViewController, animated: false, completion: nil)
}
}
Next, our DelegateViewController needs to conform to the delegate protocol:
class DelegateViewController: UIViewController, MyViewControllerDelegate {
func presentMyViewController() {
let myViewController = MyViewController()
presentViewController(myViewController, animated: false, completion: nil)
}
func foo() {
/// Respond to the delegate method.
}
}
Finally, we can make our DelegateViewController the delegate of MyViewController:
class DelegateViewController: UIViewController, MyViewControllerDelegate {
func presentMyViewController() {
let myViewController = MyViewController()
myViewController.delegate = self
presentViewController(myViewController, animated: false, completion: nil)
}
func foo() {
/// Respond to the delegate method.
}
}
That's a lot of boilerplate code that is repeated every time you want to create a new delegate. This opens you up to a lot of room for errors. In fact, the above code has a pretty big error already that we are going to fix now.
Whenever you create a delegate property on an object, you should use the weak keyword. Otherwise, you are likely to create a retain cycle. Retain cycles are one of the most common ways to create memory leaks and can be difficult to track down. Let's fix this by making our delegate weak:
class MyViewController: UIViewController {
weak var delegate: MyViewControllerDelegate?
}
This causes another problem though. Now we are getting a build error from Xcode!
'weak' cannot be applied to non-class type 'MyViewControllerDelegate'; consider adding a class bound.
This is because you can't make a weak reference to a value type, such as a struct or an enum, so in order to use the weak keyword here, we have to guarantee that our delegate is going to be a class. Let's take Xcode's advice here and add a class bound to our protocol:
protocol MyViewControllerDelegate: class {
func foo()
}
Well, now everything builds just fine, but we have another issue. Now your delegate must be an object (sorry structs and enums!). You are now creating more constraints on what can conform to your delegate. The whole point of the delegate pattern is to allow an unknown "something" to respond to the delegate events. We should be putting as few constraints as possible on our delegate object, which brings us to the next issue with the delegate pattern.
In pure Swift, protocols don't have optional functions. This means, your delegate must implement every method in the delegate protocol, even if it is irrelevant in your case. For example, you may not always need to be notified when a user taps a cell in a UITableView.
There are ways to get around this though.
In Swift 2.0+, you can make a protocol extension on your delegate protocol that contains a default implementation for protocol methods that you want to make optional. Let's make a new optional method on our delegate protocol using this method:
protocol MyViewControllerDelegate: class {
func foo()
func optionalFunction()
}
extension MyViewControllerDelegate {
func optionalFunction() {
}
}
This adds even more unnecessary code. It isn't really clear what the intention of this extension is unless you understand what's going on already, and there is no way to explicitly show that this method is optional.
Alternatively, if you mark your protocol as @objc, you can use the optional keyword in your function declaration. The problem here is that now your delegate must be an Objective-C object. Just like our last example, this is creating additional constraints on your delegate, and this time they are even more restrictive.
The delegate pattern only allows for one delegate to respond to events. This may be just fine for some situations, but if you need multiple objects to be notified of an event, the delegate pattern may not work for you. Another common scenario you may come across is when you need different objects to be notified of different delegate events.
The delegate pattern can be a very useful tool, which is why it is so widely used, but recognizing the limitations that it creates is important when you are deciding whether it is the right solution for any given problem.
Anthony Miller is the lead iOS developer at App-Order in Las Vegas, Nevada, USA. He has written and released numerous apps on the App Store and is an avid open source contributor. When he's not developing, Anthony loves board games, line-dancing, and frequent trips to Disneyland.