Separating Concerns With The Delegate Pattern

ViewType { return view } else { let view = ViewType() self.

view = view return view } }}And here is our BaseView, that does the same type of thing for our Views:class BaseView: UIView { convenience init() { self.

init(frame: .

zero) } override init(frame: CGRect) { super.

init(frame: frame) setupView() } required init?(coder aDecoder: NSCoder) { super.

init(coder: aDecoder) setupView() } func setupView() { // Use this method to do any set up that is shared between all your views, // for example drawing backgrounds, etc.

}}The actual Controller and View that we are going to work with are going to start out like this:class MyController: BaseViewController<MyView> { }class MyView: BaseView { private let button: UIButton = { let button = UIButton(frame: .

zero) button.

setTitle("This is a button!", for: .

normal) button.

setTitleColor(.

black, for: .

normal) button.

setTitleColor(.

white, for: .

highlighted) button.

backgroundColor = .

lightGray button.

translatesAutoresizingMaskIntoConstraints = false button.

heightAnchor .

constraint(equalToConstant: 60).

isActive = true button.

widthAnchor .

constraint(equalToConstant: 200).

isActive = true return button }() override func setupView() { super.

setupView() self.

backgroundColor = .

white self.

addSubview(self.

button) self.

button.

centerXAnchor .

constraint(equalTo: self.

centerXAnchor).

isActive = true self.

button.

centerYAnchor .

constraint(equalTo: self.

centerYAnchor).

isActive = true }}A wonder of UI design, don’t you agree?We’ve got a MyController class that will instantiate a MyView object.

MyView only contains and displays a button that we are going to use to receive user input.

Getting User InputOur original problem was that we wanted to receive and respond to user input, but we didn’t want our View to intrude on the Controllers responsibilities, and vice versa.

We also want to keep from tightly coupling our two classes, in case we want to replace one of them later on.

What if we added the following functionality to MyView?public func setButtonAction(target: Any?, action: Selector) { self.

button.

addTarget(target, action: action, for: .

touchUpInside)}By doing so, we could call the following bit of code in MyController to set the appropriate response to a button tap:self.

typedView.

setButtonAction(target: self, action: #selector(respondToButtonTap(_:)))This is actually not a terrible solution compared to keeping all the View components in our Controller.

We may however run into some weird bugs if we connect several instances to receive signals from the button.

If any of those instances are deallocated, the selector will start looking for the next available object to receive that same signal, so the user input may end up making changes to another object than was originally intended.

To see what I mean, change the MyController class to the one below and see what happens when you hit the button:class MyController: BaseViewController<MyView> { private var counter: Int = 0 override func viewDidLoad() { self.

typedView.

setButtonAction(target: self, action: #selector(respondToButtonTap(_:))) let c = MyController() self.

typedView.

setButtonAction(target: c, action: #selector(respondToButtonTap(_:))) let d = MyController() self.

typedView.

setButtonAction(target: d, action: #selector(respondToButtonTap(_:))) let e = MyController() self.

typedView.

setButtonAction(target: e, action: #selector(respondToButtonTap(_:))) let f = MyController() self.

typedView.

setButtonAction(target: f, action: #selector(respondToButtonTap(_:))) } @objc func respondToButtonTap(_ sender: UIButton) { counter += 1 print(counter) }}s// Tap 1 prints "1 2 3 4 5"// Tap 2 prints "6 7 8 9 10" and so onThis is a contrived example, but it illustrates what could happen if several Controllers were listed as targets in the same button.

It would be a nightmare to hunt down a bug like this if it ever appeared in your code, especially since the Controllers that are initialized in viewDidLoad() are deallocated directly after the method returns.

What About The Delegate Pattern?What if we changed MyView to look like this:protocol MyViewActionDelegate: AnyObject { func buttonWasTapped(_ sender: UIButton) -> Void}class MyView: BaseView { public weak var buttonActionDelegate: MyViewActionDelegate?.private let button: UIButton = { let button = UIButton(frame: .

zero) button.

setTitle("This is a button!", for: .

normal) button.

setTitleColor(.

black, for: .

normal) button.

setTitleColor(.

white, for: .

highlighted) button.

backgroundColor = .

lightGray button.

translatesAutoresizingMaskIntoConstraints = false button.

heightAnchor .

constraint(equalToConstant: 60).

isActive = true button.

widthAnchor .

constraint(equalToConstant: 200).

isActive = true return button }() override func setupView() { super.

setupView() self.

backgroundColor = .

white self.

addSubview(self.

button) self.

button.

centerXAnchor .

constraint(equalTo: self.

centerXAnchor).

isActive = true self.

button.

centerYAnchor .

constraint(equalTo: self.

centerYAnchor).

isActive = true self.

button.

addTarget(self, action: #selector(self.

delegateButtonTap(_:)), for: .

touchUpInside) } @objc private func delegateButtonTap(_ sender: UIButton) { self.

buttonActionDelegate?.

buttonWasTapped(sender) }}And MyController to look like this:class MyController: BaseViewController<MyView>, MyViewActionDelegate { override func viewDidLoad() { self.

typedView.

buttonActionDelegate = self } func buttonWasTapped(_ sender: UIButton) { // Do your thing }}What happens here is that MyView signs up as the receiver of button events, eliminating the risk of registering multiple targets.

MyView then delegates the button event to a single action delegate through a well defined protocol.

It doesn’t matter if we switch the Controller, as long as it conforms to MyViewActionDelegate, it will be totally fine and only one signal will be sent.

Thanks to the optional chaining, it’s guaranteed that the signal will only be sent if buttonActionDelegate holds a reference to another object.

If the variable is nil, the signal will be dropped and disregarded in the delegateButtonTap(_:) method.

The main gain in this solution is that it keeps our objects separated and our View unaware of the Controllers existence.

It relies solely on the use of a protocol-defined channel that may or may not have anyone listening on the other end.

The View won’t care, it will relay the signal to whoever might be there, and move on.

That’s it for this week!.Hopefully this discussion sparked a few ideas on how you can use delegates in your own projects.

Feel free to comment if you have questions, and follow to get notifications about future articles.

To learn more about iOS Development, check out my previous articles:Understanding Swift ClosuresWhat are closures and how do they really work?medium.

comIntroduction To Protocol Oriented ProgrammingWhat is Protocol Oriented Programming and how can we take advantage of its core concepts to make our code more…medium.

comThis story is published in The Startup, Medium’s largest entrepreneurship publication followed by +409,714 people.

Subscribe to receive our top stories here.

.. More details

Leave a Reply