Callbacks and delegation are two of the most commonly used tools by Swift developers. In this article, I will try to explain the differences between them from the point of view of usage and memory management.
Delegation is a common design pattern where one class/struct delegates the responsibility of implementing some functionality to another (the delegated class or struct). In order to make communication between the delegates and the delegated modules, we use a kind of contract called a protocol.
ShowResult
that shows a result after making some arithmetics operations.public protocol ShowResult{func show(value:Int)}
A class, struct, or enum can implement this protocol as follows:
extension ViewController: ShowResult{func show(value: Int) {print(value)}}
Now we understand protocols and how to implement them using extension, but what about delegation?!
UIViewController
that we want to use for some mathematics operations, but we don’t want to implement the mathematics functions inside our ViewController
class. Maybe we should do this task in a second class or struct .ViewController
class. This is our MyIntOperation
instance that, when it finishes its work, can delegate the display to our ViewController
class :).
The ViewController
object will observe any event launched by the MyIntOperation
object, so we have a one-to-one relationship.class MyIntOperation {weak var delegate: ShowResult?func sum(firstNumber:Int, secondNumber:Int){// Make arithmetic operation and delegate display to the ViewControllerdelegate?.show(value: firstNumber + secondNumber)}}
The delegate type is a protocol, and every class/struct that implements this protocol can make the call. Maybe when we make some unit tests for the MyOperationClass
, we won’t be dependent on the ViewController
instance 😃.
We notice that the delegation pattern respects the fifth principle of SOLID because we only infer to abstractions in the ShowResult
protocol, not to concretions.
ViewController
keeps a reference of the MyIntOperation
, which keeps a reference of the delegate that is simply a ViewController
instance. This is why ARC cannot release the ViewController
when, for example, we go to a second view.3.2 Weak reference:
When the ViewController
is created, ARC increases the reference count to 1. When MyIntOperation
keeps a reference to the ViewController
(delegate is simply a reference to it), ARC increments the count to 2. Imagine that we want to push a new ViewController
(i.e., when we call the system’s Deinit method because the instance should be nil), but since we have a delegate reference kept by the MyIntOperation
object, our retain count will be equal to 1 (not zero). Therefore, we cannot release our ViewController
instance.
The solution is to declare the delegate weak so that the retain count will not be increased
and ARC can deallocate the ViewController
instance.
In this first part, we studied the delegation design pattern and the relationship it has with memory management. The main idea is that this pattern is a communication type pattern that could be a source of retain cycles. It gives a good opportunity to apply DI principles based on protocols. In the second part of this tutorial, we will speak about closure callback and how we can observe an event between objects using a closure.