Trusted answers to developer questions

Delegates & callbacks in Swift (part 1)

Get the Learn to Code Starter Pack

Break into tech with the logic & computer science skills you’d learn in a bootcamp or university — at a fraction of the cost. Educative's hand-on curriculum is perfect for new learners hoping to launch a career.

Introduction

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.

What is delegation?

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.

  1. Protocol:
    A protocol specifies the initializers, properties, functions, and subscripts required for a Swift object type (class, struct, enum) to conform to the protocol.
    For example, we can create a protocol called 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?!

  1. Delegation and protocol:
    Now, imagine that we have a 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 .
    Basically, we should have an instance of the second class/struct in the 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 ViewController
delegate?.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.

  1. Memory management:
    Memory management is very important in any application. Swift uses Automatic References Counting (ARC) to keep track of the strong references to instances of classes, and to increase or decrease their reference count. ARC does not increase or decrease the reference count for value types, like struct or enum, because these are copied when assigned.
    3.1. Strong reference cycle:
    The retain cycle is when two classes depend on each other, so we cannot release any of them. But why?
    The 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.
Retain cycle
Retain cycle

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.

Conclusion

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.

RELATED TAGS

closure
delegation
swift
arc

CONTRIBUTOR

Walid SASSI
Attributions:
  1. undefined by undefined
Did you find this helpful?