Memory leaks by custom delegate
Working with delegates is a common practice in the coding world of iOS and macOS developers. Many of the core APIs like UITableView have delegates that are called to execute functions so they can get or pass information to another control.
It is easy to use as closures don't always fit the needs.
You can pass the same delegate object to multiple items that will execute the delegate methods. As a delegate is always a protocol, you are safe to use it in code. You have code completion for methods with all the parameters and you know what is available as everything is defined by the protocol.
I use custom delegates from time to time, but I stumbled over some issues recently that caused major memory leaks in my apps.
Take this example:
protocol MyDelegate {
func someDelegateFunction()
}
class CustomView: UIView {
var delegate: MyDelegate?
}
class MyViewController: UIViewController, MyDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let newView = CustomView()
newView.delegate = self
self.view.addSubview(newView)
}
// MyDelegate
func someDelegateFunction() {
print("Do stuff")
}
}
This construct is creating a memory leak.
Why?
Look at ARC. ARC was introduced by Apple in 2011 and automatically deallocates objects from the memory when they are not needed anymore. You don't have to do this manually. Most apps and frameworks use ARC, because it just works. Well... Most of the time it just works. Not in cases like the one we created at the top.
So what happened? If not specified in any other way, all properties hold a strong
reference to their object.
This is OK, if you don't create a loop of strong references. And that's what we did.
The ViewController holds the View, which has a delegate that is looping back to the ViewController. If we declare this back reference as weak, everything will work fine and retained memory will be deallocated as soon as we dismiss our ViewController.
Solution
It is as easy as making the delegate property weak:
class CustomView: UIView {
weak var delegate: MyDelegate?
}
But you will get the following error:
'weak' may only be applied to class and class-bound protocol types, not 'MyDelegate'
The solution is simple, just modify your delegate declaration to make it class-bound:
protocol MyDelegate: class {
func someDelegateFunction()
}
Noticed the : class
I added? That's it! Without this addition, even a struct can be used as a delegate, which can't be hold in a weak reference. A class can and now will.
SwiftLint to the rescue
I use SwiftLint in my projects to apply a consistent coding style and be sure everything is documented properly.
The following rules help you prevent this issue: weak_delegate
and class_delegate_protocol
. These are both enabled by default, so make sure you didn't opt-out un your .swiftlint.yml
.
Below you find an old version of the custom rules I used before SwiftLint implemented similar rules on their own.
My old custom rules
To prevent this memory leaks from happening again, I added two custom rules to my .swiftlint.yml
:
delegate_classprotol:
name: "class based delegate protocol"
regex: "protocol[\s]+[\S]+([dD]elegate)[\s]*\{"
message: "Prevent memory leaks and define delegates as class based protocols so we can create weak references"
severity: error
match_kinds: identifier
weak_delegate:
name: "Prefer weak delegates"
regex: "^(\s|\t)*(var|let)\s+[\S]*[dD]elegate"
message: "Prevent memory leaks and refer delegates as weak"
severity: error
match_kinds: identifier
Just add them to the custom_rules
block.
Note: This requires that the protocol identifier ends with delegate
. It doesn't matter if uppercase or lowercase D.
The same rule goes for the property name.