Trusted answers to developer questions

Delegates & callbacks in Swift (part 2)

Free System Design Interview Course

Many candidates are rejected or down-leveled due to poor performance in their System Design Interview. Stand out in System Design Interviews and get hired in 2024 with this popular free course.

Introduction

In the first part of this tutorial about delegation and callbacks, I had presented the delegation design pattern, which is a communication design pattern between two objects based on a one-to-one relationship. We spoke about memory management, how the Delegation pattern generates a retain cycle, and how we can resolve the issue using the weak attribute.

In this second part, we will learn about callbacks using a closure, and how we can communicate between two objects using a closure. We will also observe how callbacks affect memory management.

Closures in Swift

Closures in Swift, like blocks in Objective C, declare and capture a piece of executable code that will be launched at a later time. You can give it a dedicated type name to be referenced and used later, like this:

// you can declare the type of closure using typealias attribut
typealias completion = (Int) -> Void?
// you can now declare it simply like a variable

A closure is simply a function with no name; you can pass it to a variable or pass it around like any other value. The idea is to make the same thing that we made with delegation using closure callbacks. We have our two classes, ViewController and MyIntOperations, but we don’t need a protocol or a delegate object to print the value calculated in the ViewController coming from the MyIntOperations instance.

How a closure works

A closure is simply a function, so we need to define it and call it.
In our case, the closure will return the value calculated by the MyIntOperations instance.

// we add a new parameter to the sum function which
// is the closure named callBack and its type is completion
// defined above
func sum(firstNumber:Int, secondNumber:Int,callBack:completion){
// we call our closure function with
// the value calculated
callBack(firstNumber + secondNumber)
}

Now, we move to what we have to do with this closure, i.e., the definition. This part of the code exists in the ViewController class:

// we call the sum function and also define
// what we will do with our closure when it is called
// that's all!
myIntOperation.sum(firstNumber: number1, secondNumber: number2) { value in
// define what we will do with the value
// for example simply print it
self.showResult(result: value)
}
private func showResult(result : Int){
print(result)
}

Memory management with closures

Classes aren’t the only kind of reference type in Swift. Functions (include closures) are reference types too. If a closure captures a variable holding a reference type like self.showResult(result:value), the closure will maintain a reference to it. We now have a retain cycle because the ViewController keeps a reference to the MyIntOperation, which maintains a reference to the ViewController itself in order to execute the closure callback.

But how we can resolve this???

In the definition of the closure, we have extra parameters that we collectively call the capture list. Basically, we use the capture list to define how objects inside the definition of the closure should be referenced. So, we have to mention our ViewController (self) as weak:

// define self as weak in order to not increase
// reference counting to it by the closure
myIntOperation.sum(firstNumber: number1, secondNumber: number2) {[weak self] value in
self?.showResult(result: value)
}

Conclusion

Closures are a good solution to handle communication between two objects properly. However, a retain cycle can be created when a closure maintains references to some objects inside its definition. A closure is based on a one-to-one relationship like delegation. Delegation respects the DI inversion principle but closures do not.

RELATED TAGS

closure
swift
callback
memory management
Did you find this helpful?