Table of contents
1.
Introduction
2.
Non-Escaping Closures
2.1.
Example
2.2.
Output
3.
Escaping Closures
3.1.
Example (Storage)
3.1.1.
Output
3.2.
Example (Asynchronous Execution)
3.2.1.
Output
4.
Why Non-Escaping Closures by Default?
5.
Frequently Asked Questions
5.1.
What is the dispatch queue?
5.2.
What is the handler?
5.3.
What is a weak self?
5.4.
Can we use the weak self in dispatch?
6.
Conclusion
Last Updated: Mar 27, 2024
Medium

Swift Escaping and Non-escaping Closures

Introduction

Let’s say we have defined a variable in a context and we are using the variable somewhere else in the context, let’s say a function. If we try to use it to function somewhere else in our code we need to refer to the variable in the context as well. However, this usually won’t be possible in a regular sense. This is where Closures come into play. 

Closures basically store a reference to any constant and variable in a context in which they are defined so that they can be used anywhere they may be needed. This is known as closing over the variables and constants in question. Closures are basically blocks of code that can be defined as self-contained in the sense as they can be passed around in methods and can be executed in the method as well. 

Till Swift 2, closures were escaping by default which basically means that closure can escape during the function body execution if not marked as nonescaping. However, from Swift 3 onwards it is set to nonescaping by default. In this blog, we will learn about the escaping and non-escaping closures and why the changes in the default happened.

Non-Escaping Closures

When we pass a closure as an argument in a function, the closure gets executed along with the function’s body and returns the compiler back. As soon as the execution completes the closure that we initially passed goes out of scope as well and it can not be found in the memory anymore.

The lifecycle of any nonescaping closure is as follows:

  • The closure is passed as an argument during the function call.
  • The function is executed as it should
  • Function comes along the closure and executes
  • The function completes execution and returns the compiler back

Example

func find_max(_ array:Array<Int>, performMax:((Int) -> Void)){  
    var max: Int = 0
      //finding the max element of an array
for value in array{
if(value>max){
        max=value
        }
}
    performMax(max)  
}  
  
// a normal function call in closure will be non-escaping as the default is non-escaping.
func calculation(){  
    //create an array and pass it into the function find_max.
    let array = [542,45,459,12,9]  
    find_max(array) { (max) in  
        //print output
        debugPrint(max)  
    }  
}  

 
//call the function
calculation()
//closure has no existence in memory after execution 

Output

In the above example, the closure is not escaping the execution and after the print statement it will have no existence in the memory

Escaping Closures

When we pass a closure as an argument in a function the closure is basically saved to be executed later and the function’s body gets executed and returns to the compiler back. However, after the execution ends, the scope of the passed closure still exists in the memory until the closure gets executed.

There are two ways to escape a closure in Swift:

  • Storage: We can preserve the closure in storage in memory it acts like waiting for the API response after the calling function gets executed and the compiler is returned.
  • Asynchronous Execution: When a closure is being executed asynchronously on a dispatch queue, the queue stores the closure in memory for us to use in the future. Thus, we have no idea when the closure will get executed.

The lifecycle of an escaping closure is as follows: 

  • The closure is passed as an argument during the function call
  • The functions code gets executed
  • The function executes the closure asynchronously or is stored
  • The function returns the compiler back

Example (Storage)

//create a variable similar to the closure type
var compilationHandler: ((Int)-Void)?
var max = 0

 
//accepts an escaping closure
func find_max(_ array:Array<Int>, performMax:@escaping((Int) -> Void)){  
    var max: Int = 0
    for value in array{
        if(value>max){
      max=value
        }
    }
    //closure preserved in compilationHandler
    compilationHandler = performMax 
}  
  
  
func calculation(){  
    let array = [542,45,459,12,9]  
    find_max(array) { (max) in  
        debugPrint(max)  
    }  
}  

 
//function call is made
calculation()
//compilationHandler is called after calculation
compilationHandler!(max)
//escaping closure preserved

Output

Example (Asynchronous Execution)

func find_max(_ array:Array<Int>, performMax: @escaping ((Int) -> Void)){  
    var max: Int = 0
for value in array{
if(value>max){
        max=value
        }
}
    //closure executed asynchronously on DispatchQueue 
    DispatchQueue.global().asyncAfter(deadline: .now() + 0.5) {  
        //closure preserved in queue to be used in future
        performMax(max)  
    }  
    //we cannot determine when the closure gets executed 
}  
  
  
func calculation(){  
    let array = [542,45,459,12,9]  
    find_max(array) { (max) in  
        debugPrint(max)  
    }  
}  
calculation()  

Output

The @escaping attribute is very important without which it will throw an error.

Why Non-Escaping Closures by Default?

Non-escaping closures come with a myriad of benefits. The most beneficial is performance and code optimization as a non-escaping closure will handle memory allocation for the closure by the compiler.

Also using non-escaping closures we can self without any problems and don’t need to use weak self as the closure executes before the function returns so self is up for use.

Frequently Asked Questions

What is the dispatch queue?

A dispatch queue is a FIFO queue that the application uses to queue the tasks in the form of block objects. Dispatch queues execute these tasks either serially or concurrently.

What is the handler?

Handlers are basically a callback function that calls when a task completes.

What is a weak self?

Weak self is a keyword that tells the compiler to create a weak reference to itself and prevents memory leaks in our application. It lets the ARC release itself from memory when necessary.

Can we use the weak self in dispatch?

The weak self is only required when a strong self will end up causing a retain cycle within the same object.

Conclusion

In this blog, we discussed the escaping and non-escaping closures in Swift.

You may want to learn more about Swift hereWe hope that this blog has helped you enhance your knowledge regarding closures in Swift. Do upvote our blog to help other ninjas grow.

Learning never stops, and to feed your quest to learn and become more skilled, head over to our practice platform Coding Ninjas Studio to practice top problems, attempt mock tests, take on guided paths, read interview experiences, try our interview bundle and much more.!

Happy Learning!

Live masterclass