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, 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 attributtypealias 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.
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 abovefunc sum(firstNumber:Int, secondNumber:Int,callBack:completion){// we call our closure function with// the value calculatedcallBack(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 itself.showResult(result: value)}private func showResult(result : Int){print(result)}
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 closuremyIntOperation.sum(firstNumber: number1, secondNumber: number2) {[weak self] value inself?.showResult(result: value)}
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.