Delegate Pattern
The Delegate Design Pattern is a very useful tool to have under your belt. It helps with keeping things simple, modularity, loose coupling, and much more, but it comes with a small learning curve.
What is the Delegate Pattern?
The Delegate is a way to implement communication between objects in which one object (the delegate) handles certain events or behaviours on behalf of another object.
For example, you have Object A. It creates an Object B to perform a task for him. At the end of the task, we need to update Object A with the result, of course, we could make Object B manually update Object A, but updating Object A isn't the clear responsibility of Object B.
His responsibility is only to perform said task! Because of this, to make things clear and clean, the Delegate Pattern is used here so we can have a clear distinction of responsibilities, make it easier in the future to read and modify (and also to avoid memory leaks, but more on that later).
Yeah, but… I don't quite get it yet.
It's ok, let's have a real example now!
For this example, you have Object A. Let’s call it GameScreen. The GameScreen shows the player items, how many coins he has and more. Then, the player decided to buy some coins, so, the GameScreen creates an Object B to perform a task for him. Let’s call it BuyCoinsScreen.
Now, when the user finishes buying some coins on BuyCoinsScreen, we need to go back and update the GameScreen with our new coins! But how do we do it?
We need to create first the Delegate itself (which in Swift is usually a protocol) and implement it in our GameScreen. Let's first determine our GameScreen:
class GameScreen {
var totalCoins: Int
func userWantsToBuyCoins() {
let vc = BuyCoinsScreen()
present(vc, animated: true)
}
}
Now, let's create our delegate that will be responsible for this new responsibility and implement it:
protocol EntityDelegate: AnyObject {
func updateCoins(with newValue: Int)
}
extension GameScreen: EntityDelegate {
func updateCoins(with newValue: Int) {
totalCoins += newValue
}
}
Don't forget to make the protocol conform to AnyObject! Without that, we will get an error!
Now, we should add to our BuyCoinsScreen the delegate reference and make it use it:
class BuyCoinsScreen {
weak var delegate: EntityDelegate?
func userBoughtCoins(_ total: Int) {
delegate?.updateCoins(with: total)
}
}
Let's not forget from making the delegate reference weak, since we are after all referencing something from the father class.
Very nice! But something is missing here. We have our new Delegate, the GameScreen, its extension and our BuyCoinsScreen, but we are not passing the delegate reference to our BuyCoinsScreen! If we don't do that, our delegate will never be called.
So, let's do that! In the GameScreen function, where we present the BuyCoinsScreen, we need to say that the delegate is the GameScreen itself.
func userWantsToBuyCoins() {
//...
vc.delegate = self
//...
}
Well, isn't that a bit weird? How could we be the delegate? Wasn't that the protocol? Well, yes, but remember that our GameScreen now conforms to the protocol itself, this way we can make the behaviour directly decoupled from our classes, making it easier not only to have clearer responsibilities but now any other class in our project can use the Delegate we wrote!
Some Observations
Naming Convention
The Delegate Design Pattern naming convention in iOS development follows a specific format, where the protocol name typically ends with the suffix “Delegate”. For example, UITableViewDelegate
, UITextFieldDelegate
, and UIApplicationDelegate
are all protocols that follow this naming convention.
But why should I do it? Because it’s usually a good practice to follow Apple’s naming conventions when creating delegates or other protocols since it makes the code more consistent and easier to understand for other developers who may be working on the same project.
When Apple defines a protocol with a specific name, such as UITableViewDelegate
, it provides a clear indication of the intended purpose of that protocol. By using the same naming convention in our own code, we can provide a clear indication of what our protocol is meant to do and how it should be used.
Additionally, following Apple’s naming conventions can also help with code completion and other IDE features, which can save time and improve productivity when writing code. By using familiar and standardized names, it’s easier for developers to find the appropriate methods and properties when working with delegates or other protocols.
Memory management and reusability
We could pass the information between our views in a couple of different ways. For example, we could have a GameScreen reference at BuyCoinsScreen, so we could update it there:
But, doing so would bring us some drawbacks:
- The BuyCoinsScreen now has two responsibilities: buying coins and updating another view (making it highly coupled with the GameScreen — making it harder to use elsewhere).
- And, with the way Swift manages memory, if the GameScreen would ever be de-instantiated, that would keep the BuyCoinsSreen in memory (instead of removing it), causing memory leaks.
To solve the 2nd problem, we could make the reference weak, but that would not resolve the first problem.
Because of it, the delegate pattern is amazing here. Imagine we want to access the BuyCoinsScreen from other views, like the Shop and the Item Storage views. Using the delegate pattern, our BuyCoinsScreen does not know who called him, and now any of these screens can use fully the capabilities of this feature!
With this, we can have responsibilities that are clearly separated, making it easier to read and expand in the future.
Another very useful example you could use it for is a Login feature: our login could either succeed or fail and for each case, we should tell our user what exactly happened (login successful, no internet, wrong password, etc.).
It's not a tool for all cases
Although very useful, it's hard to not use it for everything once you get the hang of it. And, if not being careful, this can worsen your code instead of helping:
- They can add complexity to your code, especially if you have many delegates or complex delegation hierarchies. In these cases, it may be better to use a different design pattern that is better suited to your needs
- Tight coupling if not used correctly. This can make your code more difficult to modify or replace in the future.
Overall, the Delegate Design Pattern is a useful tool in iOS development, but it’s important to weigh the benefits and drawbacks before deciding whether or not to use it in your code.
Code examples
I have prepared a GitHub repository containing a bare-bone example and a kinda real-life example of the Delegate Design Pattern. I hope this helps you in the future as a "cheat sheet" while you are learning this pattern or just remembering to use it with a project.
I hope this helped, make sure to like and comment if you have any doubts about this design pattern!