iOS: How To Make Weak Delegates In Swift
Here is a common scenario: You have a ViewController with multiple views and you want to delegate some action logic from the View to the ViewController.
For example, let’s say you have a view with a button in it (such as “Sign Up” button in a form), and when the user clicks that button, you want to delegate the logic (validation and API call) for that button to the ViewController to figure out where the user should go (or not) next.
Your code would look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
// The protocol to delegate the // button tap to the ViewController protocol ButtonDelegate { func onButtonTap(sender: UIButton) } class ViewWithTextAndButton: UIView { // keeping track of the delegate to use later var delegate: ButtonDelegate? func onButtonTap(sender: UIButton) { // invoking the delegate when the // button is actually tapped delegate?.onButtonTap(sender) } } class MyViewController: UIViewController, ButtonDelegate { let viewWithTextAndButton = ViewWithTextAndButton(frame: CGRect(x: 0, y: 0, width: 100, height: 100)) override func viewDidLoad() { super.viewDidLoad() // assigning the delegate viewWithTextAndButton.delegate = self view.addSubview(viewWithTextAndButton) } // MARK: ButtonDelegate // the delegated logic is here func onButtonTap(sender: UIButton) { print("This button was clicked in the subview!") } } |
But there is one BIG problem! Since the View is keeping a strong reference to the ViewController as the delegate and the ViewController is keeping a strong reference to the View, this creates a retain cycle. The ViewController references the View, and the View references the ViewController, ensuring that neither one will ever have the retain count of 0, so neither will be deallocated.
The solution is to make one of them hold on to the other weakly! So how do we do this in Swift? We have to constrain the protocol to only be used by Reference Types by adding the class keyword:
1 2 3 4 |
// This Protocol can now only be used by Classes! protocol ButtonDelegate: class { func onButtonTap(sender: UIButton) } |
Now, we can say that we want our delegate to be weakly retained:
1 2 3 4 5 6 7 8 9 10 11 |
class ViewWithTextAndButton: UIView { // Notice that we can now use the weak keyword! // this delegate now refers to a reference type only (UIViewController) // and that class will be weakly retained weak var delegate: ButtonDelegate? func onButtonTap(sender: UIButton) { delegate?.onButtonTap(sender) } } |
That’s it!
This is another great example as to why you should attempt to use value types when possible – to avoid this problem. With value types, values are copied, so not memory leaks like the one above can occur. But, of course, we have to work within the Cocoa framework, which includes a lot of subclassing (like that of UIView and UIViewController), so that’s when you need to use the constrained class protocols!