Delegate Pattern and Strong Reference Cycle In Swift
This post is for self-documenting purpose but if anyone finds it helpful, I’ll be glad.
Memory management is built into the language itself in Swift, called ARC (Automatic Reference Counting). Refer to this and this.
For most mundane works, ARC works fine and you may safely strong reference your objects. However, as your code becomes more adventurous and intertwined (not necessarily due to the failure in design but rather the sheer complexity of the program), your program will have a few or more strong reference cycle.
Swift provides weak
and unowned
to let you avoid these cases as explained in this post.
Delegate Pattern
Delegate pattern is a simple and yet very powerful design pattern in iOS/OSX. It lets the delegating object to hold a reference to delegate in order to pass message in the future at its will.
This pattern is widely used in UIKit
where the framework object acts as a delegating object and your ViewController
acts as a delegate by implementing the protocol
stipulated by the delegating object.
Let’s look at an example.
UITableViewController
is the delegating object and UITableViewControllerDelegate
is the protocol
defined by delegating object that must be implemented by wanna-be delegate.
MyTableViewController
is my custom object that subclasses UITableViewController
, and can also choose to implement UITableViewControllerDelegate
to act upon delegate methods. Most prominent use for delegate pattern is to update UI (the responsibility of delegate) in response to events sent by delegating object (in the form of simply calling the defined delegate methods).
Most people, and rightly so, choose to implement UITableViewControllerDelegate
within the custom UITableViewController
itself, which is, in this case, MyTableViewController
. But remember that MyTableViewController
is a subclass of UITableViewController
? Now it’s also a delegate of UITableViewController
.
It’s a delegate
and subclass
of UITableViewController
. If you think about it, this is clearly a Strong Reference Cycle since MyTableViewController
subclasses UITableViewController
, which means delegate
to UITableViewController
is, by extension, a delegate
to MyTableViewController
, which is itself.
But don’t worry. This is how Apple intended us to use it by keeping a weak
reference to delegate in UITableViewController
’s implementation. Most, if not all, delegates in UIKit are weak
ly referenced. This is to avoid Strong Reference Cycle
caused by requiring one object to be both subclass
and delegate
.
Big win for Apple here.
When you design this delegate pattern yourself, be like Apple and make sure to mark delegate as weak
if you intend that class to be both subclass
ed and delegate
d by the same object, which is a commonplace.
This rule applies equally when your custom object holds a strong reference to delegating object and your custom object is, in turn, the delegate of that retained delegating object.
For example, think of this imaginary AudioPlayer
class. In MyTableViewController
, I want to play an audio so I instantiated by self.audioPlayer = AudioPlayer()
. AudioPlayer
also provides some delegate methods to notify client when the audio has reached the end.
protocol AudioPlayerDelegate {
func audioPlayerDidReachEnd()
}
When the audio stops, I need to update UI so I decided to make my MyTableViewController
as a delegate of AudioPlayer
by implementing AudioPlayerProtocol
and passing itself by audioPlayer.delegate = self
.
This is, again, a Strong Reference Cycle
because MyTableViewController
strongly references self.audioPlayer
, while self.audioPlayer
, again, strongly references self
, which is MyTableViewController
, the delegate of AudioPlayer
.
This type of cycle can be just as easily broken by one of the two ways.
One is to mark self.audioPlayer
as weak
.
class MyTableViewController {
private weak var audioPlayer:AudioPlayer!
override viewDidLoad() {
self.audioPlayer = AudioPlayer()
}
}
Or if AudioPlayer
’s implementation is available, simply mark the delegate
as weak
.
class AudioPlayer {
private weak var delegate:AudioPlayerDelegate!
init(delegate:AudioPlayerDelegate) {
self.delegate = delegate
}
}
First approach is unsound both semantically and architecturally Semantically, because self.audioPlayer
really should never be nil
as long as MyTableViewController
is alive and architecturally, because class (or, more properly server) fails to encapsulate its implementation from the client.
Second approach is the way to go. Semantically, delegating object is less concerned with the existence of delegate than the other way around. Delegating object must be retained to properly notify delegate as long as the delegate is alive while delegate object may be released even though delegating object is still alive. In such case, delegating object will simply stop sending messages.
Addendum
You may just as well use unowned
in place of weak
if the delegate is set within init()
method.
weak
can only be used with var
since the nature of a reference being weak
means that the refereneced object may at anytime be released.
By the same token, unowned
must be used with let
only. It implicitly asserts that the referenced object must be alive throughout its user’s lifetime.
Use unowned let
whenever you set the reference in init()
method and weak var
for everything else.