Skip to content
  • Home
  • About us
  • Contact
  • Privacy Policy
  • Disclaimer
  • Swift Online Compiler
iOS Interview Questions and Tutorials

iOS Interview Questions and Tutorials

Closures in Swift: Brief Explanation with Code Examples

Posted on November 27, 2023July 7, 2024 By Sid No Comments on Closures in Swift: Brief Explanation with Code Examples

Closures in Swift: Brief Explanation with Code Examples

Table of Contents

Toggle
  • Closures in Swift:
    • Closure Expression Syntax
    • Simplifying Closure Syntax
      • Type Inference
      • Shorthand Argument Names
      • Implicit Return
      • Trailing Closure Syntax
    • Capturing Values
    • Autoclosures
      • An example with autoclosure and without autoclosure:
      • Benefits of @autoclosure
    • Escaping Closures
      • Example :
      • Step-by-Step Explanation
      • Under-the-Hood Working
    • Non-Escaping Closures
      • Example:
      • Step-by-Step Explanation:
      • Under-the-Hood Working
      • Capturing Values in Non-Escaping Closures
      • Conclusion
    • Real-World Scenarios for Using Escaping and Non-Escaping Closures:
      • Detailed Explanation
    • Retain cycle in closure with example:
      • Example of Retain Cycle in Closure
      • Breaking the Retain Cycle
    • Frequently Asked Interview Questions on Closures and Capturing Closures with Code Examples:
      • Read More:

Closures in Swift:

A closure is a block of code that can be executed later.

Closures in Swift can capture and store references to variables and constants from the context in which they are defined. This is known as closing over those variables.

The closure can then refer to and modify the values of those captured variables, even if the scope that defined the variables no longer exists.

Closure Expression Syntax

The most general form of a closure expression syntax is:

{ (parameters) -> returnType in
    statements
}

For example, a closure that adds two integers:

let addClosure = { (a: Int, b: Int) -> Int in
    return a + b
}

This closure can be called like a function:

let sum = addClosure(3, 5)
print(sum)  // Output: 8
Simplifying Closure Syntax

Swift allows you to simplify closure syntax using type inference, shorthand argument names, and trailing closure syntax.

Type Inference

Swift can infer the types of parameters and the return type of the closure based on context, allowing you to omit them.

let addClosure: (Int, Int) -> Int = { (a, b) in
    return a + b
}
Shorthand Argument Names

Swift provides automatic argument names for inline closures, allowing you to use $0, $1, $2, etc., to refer to the closure’s arguments.

let addClosure: (Int, Int) -> Int = {
    return $0 + $1
}
Implicit Return

For single-expression closures, Swift allows you to omit the return keyword.

let addClosure: (Int, Int) -> Int = { $0 + $1 }
Trailing Closure Syntax

If a closure is the last argument in a function, you can write it as a trailing closure outside the parentheses.

func performOperation(a: Int, b: Int, operation: (Int, Int) -> Int) -> Int {
    return operation(a, b)
}

let result = performOperation(a: 2, b: 3) { $0 + $1 }
print(result)  // Output: 5
Capturing Values

Closures can capture and store references to variables and constants from their surrounding context. This allows them to work with those values even after the original scope has ended.

func makeIncrementer(incrementAmount: Int) -> () -> Int {
    var total = 0

    let incrementer: () -> Int = {
        total += incrementAmount
        return total
    }
    return incrementer
}

let incrementByTwo = makeIncrementer(incrementAmount: 2)

print(incrementByTwo())  // Output: 2
print(incrementByTwo())  // Output: 4

In this example, the incrementer closure captures the total variable and incrementAmount constant from its surrounding context.

Autoclosures

An autoclosure is a closure that’s automatically created to wrap an expression that’s being passed as an argument to a function.

It doesn’t take any arguments and when it’s called, it returns the value of the expression that’s wrapped inside it.

func serve(customer customerProvider: @autoclosure () -> String) {
    print("Now serving \(customerProvider())!")
}

serve(customer: "Sid")  // Output: Now serving Sid!

This makes the code more readable and clean.

An example with autoclosure and without autoclosure:

Let’s explore an example with and without using @autoclosure to understand its benefits and differences.

Scenario: Logging Messages Based on Debug Flag:

Suppose we have a function to log messages only if a debug flag is set. We want to make sure the logging message is constructed only if necessary to avoid unnecessary performance costs.

Without @autoclosure:

Without using @autoclosure, you need to pass a closure that returns the message:

var isDebugMode = true

func log(messageProvider: () -> String) {
    if isDebugMode {
        print(messageProvider())
    }
}

// Usage
log(messageProvider: { "This is a debug message" })

In this example:

  • The log function takes a closure messageProvider that returns a String.
  • The message is generated only if isDebugMode is true, avoiding the cost of creating the message when not in debug mode.
  • The usage of the log function requires explicitly wrapping the message in a closure.

With @autoclosure:

Using @autoclosure, you can simplify the function call by automatically wrapping the provided expression in a closure:

var isDebugMode = true

func log(message: @autoclosure () -> String) {
    if isDebugMode {
        print(message())
    }
}

// Usage
log(message: "This is a debug message")

In this example:

  • The log function uses the @autoclosure attribute for the message parameter, which automatically wraps the provided expression in a closure.
  • The message is still generated only if isDebugMode is true.
  • The usage of the log function is simpler and more readable because you don’t need to explicitly wrap the message in a closure.

Detailed Explanation: –

Without @autoclosure

  1. Function Definition:
func log(messageProvider: () -> String) {
    if isDebugMode {
        print(messageProvider())
    }
}
  • The log function takes a closure messageProvider that returns a String.
  • The closure is called inside the function only if isDebugMode is true.

2. Function Call:

log(messageProvider: { "This is a debug message" })
  • The message is wrapped in a closure and passed to the log function.
  • This ensures the message is created only when needed.

With @autoclosure

  1. Function Definition:
func log(message: @autoclosure () -> String) {
    if isDebugMode {
        print(message())
    }
}
  • The log function takes a parameter message with the @autoclosure attribute.
  • The provided expression is automatically wrapped in a closure.

2. Function Call:

log(message: "This is a debug message")
  • The message is provided as a simple string expression.
  • The @autoclosure attribute ensures the expression is not evaluated until it is called within the function.
Benefits of @autoclosure
  • Simplified Syntax: The function call is more concise and readable without the need for explicit closure syntax.
  • Deferred Evaluation: The expression is still evaluated lazily, only when needed, preserving the performance benefits.

Conclusion

Using @autoclosure can make your code cleaner and more expressive by automatically converting an expression into a closure. This is particularly useful for logging, assertions, and other scenarios where you want to defer the evaluation of an expression until it’s actually needed.

Escaping Closures

An escaping closure is a closure that’s passed as an argument to a function but is called after the function returns.

This means the closure escapes the scope of the function.

To indicate that a closure is allowed to escape, you write @escaping before the parameter’s type.

Example :

Let’s consider an example where we use an escaping closure to defer execution:

var completionHandlers: [() -> Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}

someFunctionWithEscapingClosure {
    print("This closure is escaping")
}

completionHandlers[0]()  // Output: This closure is escaping
Step-by-Step Explanation

Step 1: Declaration of completionHandlers Array

var completionHandlers: [() -> Void] = []
  • We declare an array completionHandlers that will store closures of type () -> Void.
  • This array can hold any number of closures that do not take any parameters and do not return any values.

Step 2: Definition of someFunctionWithEscapingClosure

func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
    completionHandlers.append(completionHandler)
}
  • The function someFunctionWithEscapingClosure takes a closure completionHandler as a parameter.
  • The @escaping attribute indicates that the closure can outlive the function call.
  • Inside the function, the closure is appended to the completionHandlers array.

Step 3: Calling someFunctionWithEscapingClosure

someFunctionWithEscapingClosure {
    print("This closure is escaping")
}
  • When someFunctionWithEscapingClosure is called, the provided closure (print("This closure is escaping")) is passed as the completionHandler parameter.
  • The function appends this closure to the completionHandlers array.

Step 4: Storing the Closure

  • The closure is stored in the completionHandlers array.
  • At this point, someFunctionWithEscapingClosure has returned, but the closure remains in memory because it’s stored in the array.

Step 5: Executing the Closure

completionHandlers[0]() // Output: This closure is escaping
  • We call the first closure in the completionHandlers array.
  • The closure executes and prints “This closure is escaping”.
Under-the-Hood Working
  1. Closure Capture: When the closure is passed to someFunctionWithEscapingClosure, it captures references to any constants and variables from its surrounding context (if any). In this case, there are no captured variables.
  2. Escaping Attribute: The @escaping attribute tells Swift that the closure might be stored and used after the function it was passed to returns. This changes how Swift manages the memory for the closure.
  3. Memory Management:
    • Without @escaping: By default, closures are non-escaping. Swift optimizes memory management for non-escaping closures by allocating memory on the stack, making them faster but limited to the scope of the function.
    • With @escaping: When a closure is marked as @escaping, Swift allocates memory for the closure on the heap. This allows the closure to exist beyond the scope of the function call.
  4. Storing the Closure: When completionHandler is appended to the completionHandlers array, the reference count for the closure is incremented. The array now holds a strong reference to the closure, preventing it from being deallocated when the function returns.
  5. Executing the Closure: When the closure is called from the completionHandlers array, it executes its stored code. If the closure had captured any variables from its original context, it would be able to access and modify them at this point.

Conclusion :

Escaping closures allow you to defer work and keep closures around for later use, even after the function that took them as parameters has returned. Understanding how Swift handles escaping closures under the hood helps you write efficient and safe code, particularly in managing memory and avoiding retain cycles.

Non-Escaping Closures

Non-escaping closures are the default type of closures in Swift.

They are guaranteed to be executed before the function they are passed to returns. This means they are used within the function’s scope and do not escape it.

Because of this, Swift can perform certain optimizations with non-escaping closures.

Example:

Let’s consider a simple example where we use a non-escaping closure:

func someFunctionWithNonEscapingClosure(closure: () -> Void) {
    closure()
}

someFunctionWithNonEscapingClosure {
    print("This is a non-escaping closure")
}
Step-by-Step Explanation:

Step 1: Definition of someFunctionWithNonEscapingClosure

func someFunctionWithNonEscapingClosure(closure: () -> Void) {
    closure()
}
  • The function someFunctionWithNonEscapingClosure takes a closure closure as a parameter.
  • The closure is of type () -> Void, meaning it takes no parameters and returns nothing.
  • The function immediately calls closure() within its body.

Step 2: Calling someFunctionWithNonEscapingClosure

someFunctionWithNonEscapingClosure {
    print("This is a non-escaping closure")
}
  • When someFunctionWithNonEscapingClosure is called, the provided closure (print("This is a non-escaping closure")) is passed as the closure parameter.
  • The function immediately executes this closure within its body.
Under-the-Hood Working
  1. Closure Capture: When the closure is passed to someFunctionWithNonEscapingClosure, it captures references to any constants and variables from its surrounding context (if any). In this simple example, there are no captured variables.
  2. Non-Escaping by Default: By default, closures are non-escaping in Swift. This allows Swift to perform optimizations, as it knows the closure will not be stored or executed after the function returns.
  3. Memory Management:
    • Stack Allocation: Swift can allocate memory for non-escaping closures on the stack. Stack allocation is fast and efficient but limited to the scope of the function call.
    • No Retain Cycle Issues: Since non-escaping closures are guaranteed to be executed within the function’s scope, there’s no risk of retain cycles related to the closure itself.
  4. Executing the Closure: The closure is executed immediately within the function body. Once the closure completes, and the function returns, the memory used by the closure is deallocated if it was allocated on the stack.
Capturing Values in Non-Escaping Closures

Even though non-escaping closures are limited to the scope of the function, they can still capture and modify variables from their surrounding context.

func someFunctionWithNonEscapingClosure(closure: () -> Void) {
    closure()
}

var x = 10

someFunctionWithNonEscapingClosure {
    x = 20
}

print(x)  // Output: 20

In this example, the closure captures the variable x and modifies it within the function scope.

Conclusion

Non-escaping closures in Swift are executed within the scope of the function they are passed to, allowing Swift to optimize their memory management. This makes them fast and efficient. Understanding the default behavior and optimizations of non-escaping closures helps in writing performant and safe Swift code.

Non-escaping closures are the default and are often simpler to reason about since they do not have the complexities associated with escaping closures, such as managing retain cycles and heap allocations.

Real-World Scenarios for Using Escaping and Non-Escaping Closures:

Non-Escaping Closures
Scenario: Sorting Arrays

Non-escaping closures are commonly used in scenarios where the closure is executed immediately within the scope of a function call. A typical example is sorting an array using a custom comparison closure.

let names = ["John", "Jane", "Alex", "Chris"]

let sortedNames = names.sorted { (first, second) -> Bool in
    return first < second
}

print(sortedNames)  // Output: ["Alex", "Chris", "Jane", "John"]

In this example:

  • The sorted(by:) method takes a non-escaping closure as its argument.
  • The closure is executed immediately to compare elements in the array, and it does not escape the sorted(by:) method.

Escaping Closures
Scenario: Network Requests

Escaping closures are necessary when the closure needs to be called after the function it was passed to has returned. A common scenario is asynchronous network requests where the response may arrive at a later time.

import Foundation

func fetchData(from url: String, completion: @escaping (Data?, Error?) -> Void) {
    guard let url = URL(string: url) else {
        completion(nil, NSError(domain: "Invalid URL", code: 400, userInfo: nil))
        return
    }

    URLSession.shared.dataTask(with: url) { data, response, error in
        completion(data, error)
    }.resume()
}

fetchData(from: "https://jsonplaceholder.typicode.com/posts/1") { data, error in
    if let error = error {
        print("Error: \(error)")
    } else if let data = data {
        print("Data: \(data)")
    }
}

In this example:

  • The fetchData(from:completion:) function takes an escaping closure completion because it needs to be called after the network request completes.
  • The closure captures the network response data or error and is called asynchronously inside the URLSession data task.
Detailed Explanation

Non-Escaping Closure:

In the sorting example, the closure provided to sorted(by:) is a non-escaping closure because:

  • It is used immediately within the function to compare array elements.
  • Swift can optimize its memory management by allocating the closure on the stack since it doesn’t need to persist after the function returns.

Benefits of Non-Escaping Closures:

  • Improved performance due to stack allocation.
  • Simplified memory management without the risk of retain cycles.

Escaping Closure:

In the network request example, the closure provided to fetchData(from:completion:) is an escaping closure because:

  • The network request is asynchronous, and the closure must be retained and called after the function returns.
  • The closure is stored and executed later when the network request completes, requiring it to “escape” the scope of the function.

Benefits of Escaping Closures:

  • Enable asynchronous operations where the closure needs to be executed after a delay or on a different thread.
  • Allow deferred execution of code, which is essential for tasks like network requests, file I/O, and animations.

Practical Considerations-

When deciding whether to use escaping or non-escaping closures, consider the following:

  • Use non-escaping closures when the closure is used immediately and does not need to persist beyond the scope of the function. This leads to better performance and simpler memory management.
  • Use escaping closures when the closure needs to be called after the function returns, such as in asynchronous operations or deferred execution scenarios.

By understanding the appropriate use cases and benefits of escaping and non-escaping closures, you can write more efficient and robust Swift code.

Retain cycle in closure with example:

A retain cycle occurs when two or more objects hold strong references to each other, preventing any of them from being deallocated.

In the context of closures, a retain cycle can happen when a closure captures a strong reference to self (or another object) and self holds a strong reference to the closure.

Example of Retain Cycle in Closure

Consider a class ViewController that has a method using a closure:

class ViewController {

    var title: String
    var callback: (() -> Void)?

    init(title: String) {
        self.title = title
    }

   func setupCallback() {
        callback = {
            print("Title is \(self.title)")
        }
    }

   deinit {
        print("\(title) is being deinitialized")
    }
}

var vc: ViewController? = ViewController(title: "Home")
vc?.setupCallback()
vc = nil  // ViewController is not deallocated

Explanation

  • The ViewController class has a title property and a callback property, which is a closure.
  • The setupCallback method assigns a closure to the callback property, which captures self.
  • The deinit method is used to observe when the ViewController instance is deallocated.
  • An instance of ViewController is created and assigned to the vc variable.
  • The setupCallback method is called, setting up the closure that captures self.
  • The vc variable is set to nil, but the ViewController instance is not deallocated because of the retain cycle.

Retain Cycle:

In the example above, a retain cycle is created as follows:

  • The ViewController instance has a strong reference to the closure through the callback property.
  • The closure captures and holds a strong reference to self (the ViewController instance).

As a result, the ViewController instance and the closure keep each other alive, preventing deallocation.

Breaking the Retain Cycle

To break the retain cycle, you can use a weak or unowned reference within the closure to capture self.

Using weak Reference

A weak reference does not keep a strong hold on the object it refers to, allowing the object to be deallocated if there are no other strong references.

class ViewController {

    var title: String
    var callback: (() -> Void)?

    init(title: String) {
      self.title = title
    }

    func setupCallback() {
      callback = { [weak self] in
        guard let self = self else { return }
        print("Title is \(self.title)")
      }
    }

    deinit {
      print("\(title) is being deinitialized")
    }
}

var vc: ViewController? = ViewController(title: "Home")
vc?.setupCallback()
vc = nil// "Home is being deinitialized" is printed

In this updated example:

  • The closure captures self weakly using [weak self].
  • Inside the closure, self is safely unwrapped using guard let self = self else { return }.
  • When vc is set to nil, the ViewController instance is properly deallocated, breaking the retain cycle.

Using unowned Reference

An unowned reference also does not keep a strong hold on the object it refers to, but it assumes the object will never be nil once it is set. This should be used when you are sure the object will not be deallocated while the reference is used.

class ViewController {

    var title: String
    var callback: (() -> Void)?

    init(title: String) {
      self.title = title
    }

    func setupCallback() {
      callback = { [unowned self] in
        print("Title is \(self.title)")
      }
    }

    deinit {
      print("\(title) is being deinitialized")
    }
}

var vc: ViewController? = ViewController(title: "Home")
vc?.setupCallback()
vc = nil// "Home is being deinitialized" is printed

In this example:

  • The closure captures self unowned using [unowned self].
  • When vc is set to nil, the ViewController instance is properly deallocated, and there is no retain cycle.

Conclusion

Retain cycles in closures can prevent objects from being deallocated, leading to memory leaks. By using weak or unowned references, you can break retain cycles and ensure proper memory management in your Swift applications.

Frequently Asked Interview Questions on Closures and Capturing Closures with Code Examples:

Q. How you can define a simple closure?
Ans:
A simple closure that takes no parameters and returns no value can be defined as follows:

let simpleClosure = {
    print("This is a simple closure")
}

// Call the closure
simpleClosure()// Output: This is a simple closure

In this example:

  • simpleClosure is a constant that holds a closure.
  • The closure contains a single print statement.
  • The closure is called using simpleClosure().

Q. How you can define a closure with parameters ?
Ans:
A closure can also take parameters and return a value. Here’s an example of a closure that takes two integers and returns their sum:

let addClosure: (Int, Int) -> Int = { (a, b) in
    return a + b
}

// Call the closure
let result = addClosure(3, 5)
print(result)// Output: 8

In this example:

  • addClosure is a constant that holds a closure which takes two integers (a and b) and returns their sum.
  • The closure is called using addClosure(3, 5), and the result is printed.

Q. Define a closure as a function parameter? 

Ans. In Swift, you can define a closure as a function parameter to allow the function to accept blocks of code that it can execute during its execution. This is useful for things like completion handlers, callbacks, or any situation where you need to pass some behavior into a function.

Let’s create a simple example where a function takes a closure as a parameter and calls that closure.

func performOperation(operation: () -> Void) {
    print("Starting operation...")
    operation()
    print("Operation finished.")
}

performOperation {
    print("Performing the operation")
}

Starting operation...
Performing the operation
Operation finished.

Step-by-Step Explanation:

  • The performOperation function takes a closure operation of type () -> Void as a parameter.
  • The closure operation takes no parameters and returns nothing.
  • Inside the function, the closure is called between two print statements.
  • The performOperation function is called with a closure that prints “Performing the operation”.
  • The closure is executed between the print statements in the function.

Q. What will be output of below code snippet:

var name= "Sid"

let closure = {
   print("My name is: \(name) ")
}

closure()

Ans: 

My name is: Sid 

explanation:
In Swift, closures capture and store references to any constants and variables from the surrounding context in which they are defined. This is why the closure can access the variable name even though it was declared outside the closure. The value of name at the time the closure is executed is what gets printed.

Q. What will be output of below code snippet:

var name  = "Sid"

let closure = { [name]
    print("My name is \(name) ")
}

name = "Shivi"

closure()

Ans:

My name is Shivi

Q. What will be output of below code snippet:

var name  = "Sid"

let closure = { [name] in
    print("My name is \(name) ")
}

name = "Shivi"

closure()

Ans:

My name is Sid 

Q. What will be output of below code snippet:

var name  = "Sid"
var technology = "iOS"

let closure = { [name] in
    print("My name is \(name) ")
    print("I am an \(technology)  developer")
}

name = "Shivi"
technology = "Android"

closure()

Ans.

My name is Sid
I am an Android  developer

Q. What will be output of below code snippet:

var name  = "Sid"
var technology = "iOS"

let closure = { [name, technology] in
    print("My name is \(name) ")
    print("I am an \(technology) developer")
}

name = "Shivi"
technology = "Android"

closure()

Ans.

My name is Sid
I am an iOS developer

Q. What will be output of below code snippet:

func execute() -> (Int) -> Int {
    var input = 0

    return { output in
        input += output
        return input
    }
}

let exe = execute()

let a = exe(5)
let b = exe(10)
let c = exe(15)

print(c)

Ans:

//Output
30
Read More:

Official Documentation on Closures

Top iOS Interview Questions and Answers

Blog Tags:Closures in Swift, trailing closure

Post navigation

Previous Post: Convenience initializers in Swift
Next Post: strong and weak keywords in Swift

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Most Asked iOS Interview Questions

  • Top iOS Interview Questions and Answers

Categories

  • Associated Types(7)
  • Blog
  • Dictionary in Swift(20)
  • Initializers
  • Property Wrapper
  • Singleton in Swift
  • User Defaults(4)
  • XCode 15 Errors

Recent Comments

  1. Sid on Cycle inside MyApp; building could produce unreliable results
  2. Anominous on Cycle inside MyApp; building could produce unreliable results
  3. Aisha on @objc Attribute in Swift

Recent Posts

  • Enums in Swift: Brief Explanation with Code Examples
  • Higher-Order Functions in Swift: Brief Explanation with Code Examples
  • Mutability in Structs and Classes in Swift
  • Delegate Pattern in Swift
  • resueIdentifier in Swift

DSA in Swift

  • 2D Array in Swift: Interview Questions

Copyright © 2025 iOS Interview Questions and Tutorials.

Powered by PressBook WordPress theme