Delegation pattern in Swift
How children should talk to their parents 👨👩👧👦
Let’s imagine we have two objects, FeedViewModel
& TwitterAPI
. An instance of TwitterAPI
is stored as a property called twitterAPI
inside the FeedViewModel
:
class TwitterAPI {}class FeedViewModel {
private let twitterAPI = TwitterAPI()
}
Sending a message from FeedViewModel
to TwitterAPI
is fairly easy as we can call a method or set a property on twitterAPI
, but communicating back from TwitterAPI
to FeedViewModel
is a bit more complex, because it doesn’t have a direct connection to FeedViewModel
.
Of course we could add this connection by adding a property feedViewModel: FeedViewModel
to TwitterAPI
, but this isn’t really flexible as we might want to use the TwitterAPI
in other scenarios where the parent object is no FeedViewModel
. In that case we would have to add a property for each use-case of TwitterAPI
.
One possible solution is to use the Delegation pattern.
It all starts with a protocol
protocol TwitterAPIDelegate: class {
func didRetrieveTweets(_ tweets: [Tweet])
}
Now we can add a property (usually called delegate
) to TwitterAPI
:
class TwitterAPI {
weak var delegate: TwitterAPIDelegate?
}
By setting the type of delegate
to TwitterAPIDelegate
we can ensure that the value it stores implements all methods required by the protocol. By extending FeedViewModel
to conform to TwitterAPIDelegate
it becomes one possible candidate for this value.
You may have noticed that
delegate
is a weak property. This is used to avoid a retain cycle betweenFeedViewModel
andTwitterAPI
. You can read more about retain cycles and Swift’s memory management in an upcoming post. For now just keep in mind that we need to add the: class
requirement to the delegate so that we can make the propertyweak
.
Via an extension on FeedViewModel
we can add the implementation for didRetrieveTweets
.
extension FeedViewModel: TwitterAPIDelegate {
func didRetrieveTweets(_ tweets: [Tweet]) {
// display the tweets
}
}
Inside FeedViewModel
's initializer we can set itself as the value of twitterAPI
's delegate
property:
class FeedViewModel {
private let twitterAPI = TwitterAPI()
init() {
twitterAPI.delegate = self
}
}
Using our newly created connection
Now that we established the missing connection we can notify FeedViewModel
about events from TwitterAPI
by simply calling the methods on delegate
.
class TwitterAPI {
weak var delegate: TwitterAPIDelegate?
func connect() {
// do some network calls ⏱
delegate?.didRetrieveTweets(tweets)
}
}
Because the value of delegate
is set to FeedViewModel
's self
the view model’s implementation of didRetrieveTweets
will be called. 🧙♀️
Conclusion
As you can see the Delegation pattern is a simple, yet elegant, solution for communication between child-objects and their parents / owners.
What do you think about this approach? Do you still have some questions left or do you prefer other solutions over this?
Thanks for reading ✌️