Home Articles

Unlocking Swift Closures: Code Blocks with Impact

Discover practical insights and hands-on examples for leveraging code blocks with impact.

Demo of Closures

What is Closure?

Group code that executes together, without creating a named function.

Closures are self-contained blocks of functionality that can be passed around and used in your code.

In simpler words,

  1. Closures are Callback patterns or communication patterns.
  2. Reference Types
  3. In Objective-C, we call it Blocks.

Closure Expression

Anonymous function or block of code

  1. Assigned to a variable
  2. Passed to function
  3. Return the closure

    { (parameters) -> return type in

    }

Benefits

  1. Capture and Store references
  2. Capture Lists — Useful for memory management

Good example — sorted function


    let names = ["Lorem", "ipsum", "dolor", "sit", "amet"]

    func reverseNames(_ s1: String, _ s2: String) -> Bool {
        return s1 > s2
    }

Capturing Values

Closure can capture constants and variables from the context.

The simplest form of closure that captures values is a nested function. A Nested Function can capture values from the outer function and inside the function.


    func addCount(forCount amount: Int) -> () -> Int {
        var total = 0
        func counter() -> Int {
            total += amount
            return total
        }
        return counter
    }

    // Counter doesn't have any paramter but returns Int value
    // Capturing by reference
    // Swift handles memory management involved for Capturing Values

    let countByTen = addCount(forCount: 10)
    print(countByTen()) //prints 10


    Functions and Closure are Reference Types

    let addCountbyTen = countByTen
    print(addCountbyTen()) // prints 20
    print(countByTen()) // prints 30

Trailing Closures

Can be called Special Syntax. Rather than passing in your closure as the parameter, you can pass after the function inside the braces()

If you need to pass a closure expression to a function as the function’s final argument and the closure expression is long, it can be useful to write it as a trailing closure instead.

Way to make your code look cleaner and more readable when using functions or methods that take closures as arguments.

Making your code more natural

Good example — Sorted Example: names.sorted { $0 > $1}


func makeAddOfTwoNumbers(digits: (Int, Int), onCompletion: (Int) -> Void) {
    let sum = digits.0 + digits.1
    onCompletion(sum)
}
makeAddOfTwoNumbers(digits: (7,9)) { sum in
    print(sum)
}

Escaping Closures @escaping attribute Preserved to be executed later.

Life Cycle

  1. Pass the closure as a function argument
  2. Do additional work in func
  3. Func execute async or stored
  4. Func returns the compiler back

Benefits:

  1. Storage — Preserve the closure in storage in memory, part of the calling function gets executed and returns the compiler back
  2. Asynchronous Execution — Executing the closure async on dispatch Queue, queue will hold the closure in memory, to be used for future. — No idea of when the closure gets executed

    let url = URL(string: "https://swiftpublished.com/")!
    let data = try? Data(contentsOf: url)


    // Function to call the escaping closure
    func loadData(completion: @escaping (_ data: Data?) -> Void) {
        DispatchQueue.global().async {
            let data = try? Data(contentsOf: url)
            DispatchQueue.main.async {
                completion(data)
            }
        }
    }
    loadData { data in
        guard data != nil else {
            // Handle error
            return
        }
    }

Non-Escaping Closures

They are Default Closures. Preserved to be executed immediately.

Lifecycle

  1. Pass the closure as a function argument
  2. Do additional work and execute
  3. Function returns

Benefits:

  • Immediate Callback
  • Synchronous Execution

    func getSum(of array: [Int], completion: (Int) -> Void) {
        var sum: Int = 0
        for i in 0..<array.count {
            sum += i
        }
        print(sum)
        completion(sum)
    }
    getSum(of: [1,2,3,4,5]) { sum in
        print("SumArray \(sum)")
    }

Autoclosures Automatically created to wrap a piece of code. Delay the execution of closure until it is actually needed.

Uses:

  1. When Closure is optional or
  2. Where closure’s execution may not be necessary
  3. Swift Standard Library

assert(::file:line:) precondition(::file:line:) assertionFailure(::file:line:)


    func logIfTrue(_ condition: @autoclosure() -> Bool) {
        if condition() {
            print("True")
        } else {
            print("False")
        }
    }

    logIfTrue("Saba" > "pathy")


    Autoclosure with @escaping attribute


    func delayedPrint(delay: Double, closure: @escaping @autoclosure () -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            closure()
        }
    }
    print("Start 2")
    delayedPrint(delay: 5, closure: print("Hellow Saba"))

Did you Know?

  1. Till Swift 2, Escaping closures — default
  2. Because of performance and code optimisation.
  3. Closures are Reference Types
  4. Shorthand arguments can be used — $0, $1
  5. Alternative to Delegation Pattern
  6. Closure with typealias acts as a reusable

    func execute(closure: SimpleClosure) {
        print("Executing Closure")
        closure()
    }
    let helloClosure: SimpleClosure = {
        print("Hello World")
    }
    execute(closure: helloClosure)

Please find the complete playground

This is a free third party commenting service we are using for you, which needs you to sign in to post a comment, but the good bit is you can stay anonymous while commenting.