Swift Closure: A Detailed Guide For Beginners

Swift closure is a miniature block of code, like a pocket-sized function, that you can carry around and hand out whenever needed. It’s a self-contained chunk of functionality that can be assigned to variables, passed as arguments, and even returned from other functions. Also, closures in Swift are similar to blocks in Objective-C and other programming languages.

Let’s start with a simple example:

// A closure that takes in two Ints and returns their sum
let addClosure: (Int, Int) -> Int = { (a, b) in
    return a + b
}

// Using the closure
let result = addClosure(5, 3) // Result will be 8

Here, addClosure is a closure that takes two Int parameters and returns their sum. We assign this closure to a variable addClosure using the closure syntax, which is defined within curly braces {}.

Use Cases for Swift Closure

Closures are incredibly versatile and can be used in various scenarios, such as:

  1. As Completion Handlers: They’re commonly used for asynchronous operations like network requests or file operations.
  2. Sorting: Closures can be used to define custom sorting functions for arrays and collections.
  3. Map, Filter, Reduce: These higher-order functions take closures as arguments, enabling concise and powerful operations on collections.
  4. Animation and UI Changes: Closures are frequently used in animations or UI changes to define what happens after an animation is completed.

Swift Closure Fundamentals

Let’s dive into the world of Swift closures. I’ll guide you through the fundamentals in a beginner-friendly way, so buckle up and get ready to explore more!

Closure Declaration

There are two primary ways to declare a closure:

1. Inline Closures:

These are defined and assigned directly within another piece of code, often as function parameters. Here’s the basic syntax:

let simpleClosure = { print("Hello from a closure!") }
simpleClosure() // Prints "Hello from a closure!"

This creates a closure named simpleClosure that simply prints the given message. You can then call it like any other function.

2. Trailing Closures:

Swift’s language features often prioritize clarity and expressiveness. Trailing closures are a prime example of this philosophy. Instead of placing the closure within the function’s parentheses, you write it directly after the parentheses, enclosed in braces. Here’s an example:

// Function that takes a closure as its last argument
func greet(name: String, completion: () -> Void) {
    print("Hello, \(name)!")
    completion()
}

// Trailing closure syntax
greet(name: "Kargopolov") {
    print("Have a nice day!")
}

// Output:
Hello, Kargopolov
Have a nice day!

The greet function takes a name parameter and a closure named completion. This closure is used as a trailing closure and positioned outside the function call without parentheses. You will learn more about trailing closures below.

Closure Parameters

Closures can take in parameters just like functions. 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
}

let result = addClosure(5, 3) // Result will be 8

Unlike functions, closure parameters are declared within the opening curly braces of the closure itself. They follow a similar format to function parameters: (parameterName: parameterType, ...). closure parameters don’t require explicit argument labels when you call the closure. Swift assigns default names like $0$1, and so on, based on their order.

Shorthand Argument Names

Let’s simplify things even further! Swift closures provide shorthand names for their arguments. Check this out:

let numbers = [10, 20, 5, 8, 15]

let sortedNumbers = numbers.sorted(by: { $0 < $1 })
print(sortedNumbers) // Output: [5, 8, 10, 15, 20]

In the above example, closure syntax {$0 < $1} is a shorthand for a closure expression in Swift. $0 represents the first argument (in this case, the left-hand side element being compared) and $1 represents the second argument (the right-hand side element being compared). The closure captures these two arguments and performs a comparison between them. The sorted(by:) method is part of the Swift Standard Library and is used for sorting arrays.

The shorthand argument names are represented by a dollar sign followed by a number ($0, $1, $2, and so on) and it refers to the closure’s arguments in order of their appearance. Shorthand argument names in Swift are helpful when the closure is short and its context makes it clear what each argument stands for. They trim down unnecessary details, making the code cleaner and more concise, which improves readability.

Different Types of Swift Closures

Closures in Swift are powerful tools that come in different flavors, each offering its unique functionality. Let’s delve into various types of closures and understand how they work in Swift.

Closure that Returns Value

Closures in Swift can act as self-contained functions that return values. For instance, consider a closure that multiplies two integers:

let multiplyClosure: (Int, Int) -> Int = { a, b in
    return a * b
}

let result = multiplyClosure(5, 3) // Result will be 15

In this case, the closure multiplyClosure takes two integers and returns their product, showcasing how closures can encapsulate computations.

Closures as Function Parameters

Swift’s flexibility allows passing closures as function parameters, providing a way to inject dynamic behavior into functions. Take a look at how we use a closure for an arithmetic operation:

func performOperation(_ operation: (Int, Int) -> Int) {
    let result = operation(10, 5)
    print("Result is \(result)")
}

performOperation({ $0 + $1 }) // Output: Result is 15

Here, the performOperation function accepts a closure that adds two integers. This demonstrates how closures can be passed around and executed within functions.

Escaping Closures

Escaping closures are used when a closure is called after the function is passed to returns. This scenario is common in asynchronous tasks, where a closure’s execution is delayed. Observe how escaping closures work:

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

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

// Using an escaping closure
someFunctionWithEscapingClosure {
    print("Closure has escaped!")
}

completionHandlers[0]() // Output: Closure has escaped!

Escaping closures ensures that a closure, even after the function execution, can be executed later, providing greater control in asynchronous operations.

What are Escaping Closures?

A closure is said to escape if it’s called after the function that captured it has returned. In contrast, a non-escaping closure must be executed and disposed of before the function returns.

Why are Escaping Closures Important?

Imagine you’re building a countdown timer function that takes a closure as an argument to be called when the timer reaches zero. If the closure is non-escaping, it wouldn’t be called because it wouldn’t exist anymore after the timer function (which initiated it) finishes its execution. To ensure the timer can trigger the provided action, you need an escaping closure.

Trailing Closure

Swift allows trailing closures, improving code readability by placing the closure outside the function call’s parentheses. Observe how it simplifies the usage:

func someFunctionWithClosure(closure: () -> Void) {
    // Function implementation
}

// Using a trailing closure
someFunctionWithClosure() {
    print("I'm a trailing closure!")
}

Trailing closures help in clearer code organization, especially when the closure is lengthy or complex.

Trailing closures got their name because they come after the parentheses of a function call, unlike regular arguments that go inside the parentheses. They’re placed at the end or “trail” of the function call, hence the term “trailing closure.”

Auto Closure

An auto closure is a hidden closure automatically created behind the scenes. Instead of writing the full closure syntax with braces and arrows, you just provide an expression, and Swift takes care of wrapping it in a closure for you. Consider this example:

func printResult(_ expression: @autoclosure () -> Bool) {
    if expression() {
        print("Expression is true")
    } else {
        print("Expression is false")
    }
}

// Using an autoclosure
printResult(5 > 3) // Output: Expression is true

Auto closures provide a concise way to defer the evaluation of an expression until it’s explicitly used, enhancing code simplicity.

Real-world Example of Swift Closure

Let’s delve into Swift closures using two practical examples. Witness how closures simplify tasks and enhance code flexibility in everyday Swift programming.

Weather App

Imagine you’re building a weather app that fetches data from a server. You could use closures to handle the response and update the UI accordingly:

func fetchWeatherData(completion: (WeatherData) -> Void) {
    // Simulated asynchronous network request
    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
        let weather = WeatherData(temperature: 25, condition: "Sunny")
        completion(weather) // Passes the fetched weather data to the closure
    }
}

// Calling the function with a closure
fetchWeatherData { (weather) in
    // Update UI with the fetched weather data
    print("Temperature: \(weather.temperature)°C, Condition: \(weather.condition)")
}

Here, fetchWeatherData accepts a closure as a parameter that will be called when the weather data is fetched. Once the data is retrieved, it calls the closure passing the fetched WeatherData.

In a Nutshell, Closures are nifty blocks of code that you can pass around like variables. They’re super handy for simplifying code, especially when dealing with asynchronous tasks, sorting, or performing operations on collections.

Online Store Inventory

Imagine we’re building an app to manage an online store’s inventory. In our app, we’ve got a bunch of products, each with its name and price. Let me show you how Swift closures come in handy for sorting and filtering this product list to create a seamless user experience.

Sorting Products with Closures

Picture our store’s inventory – laptops, phones, keyboards, and mice all mixed up. To organize them based on price, we’ll use a closure to sort them out.

struct Product {
    let name: String
    let price: Double
}

// Our array of products
let products = [
    Product(name: "Laptop", price: 1200.0),
    Product(name: "Phone", price: 800.0),
    Product(name: "Keyboard", price: 100.0),
    Product(name: "Mouse", price: 50.0)
]

// Sorting by price using a closure
let sortedProducts = products.sorted { $0.price < $1.price }
print("Sorted Products by Price:")
print(sortedProducts)

This closure-based sorting helps us arrange products by price, making it easier to display them from the most affordable to the most expensive.

Filtering Products with Closures

Now, imagine we want to show only the products that fit within a customer’s budget. Let’s filter the products to display those under $500:

// Filtering products under $500 using a closure
let affordableProducts = products.filter { $0.price < 500.0 }
print("\nAffordable Products under $500:")
print(affordableProducts)

This filtering closure enables us to select products that meet a specific price range, simplifying the process of displaying items that align with a user’s budget.

Function VS Closure in swift

If you’ve read the Beginner’s Guide to Swift Functions tutorial, you’d know that functions are one way to group code that executes together. Function and closure in Swift are similar in that they both encapsulate blocks of code, allowing you to perform tasks or operations. Both can capture values from their surrounding context and can be passed around as variables or arguments to other functions. However, the key difference lies in their structure and usage.

Functions are named blocks of code that perform specific tasks. They are structured and have a designated name, and you can call them by using that name throughout your code. They’re defined using the func keyword, followed by the function name, parameters (if any), return type, and the body containing the code.

For example:

func greet(name: String) -> String {
    return "Hello, \(name)!"
}

Closures, on the other hand, are like unnamed, self-contained blocks of functionality that can be used and passed around directly. They’re written within curly braces {}, don’t necessarily need a name, and can capture and store references to variables from their surrounding context.

For example:

let greetClosure = { (name: String) -> String in
    return "Hello, \(name)!"
}

The Big Difference

Understanding functions and closures forms a cornerstone in Swift programming. Functions, like neatly labeled boxes housing reusable code blocks, offer structured syntax and defined identities. Their distinct names facilitate easy recall for specific tasks. In contrast, closures, the nimble and unnamed counterparts, operate as versatile tools. They provide on-the-fly solutions, without the constraints of naming or formal structure, allowing immediate deployment precisely where needed.

  1. Structured Naming vs. Anonymous Flexibility: Functions, akin to labeled boxes, embody structured, reusable code with defined names. Closures, contrasting this, offer immediate, adaptable solutions without predefined identities.
  2. Analogous Descriptions: Functions resemble labeled boxes, called upon by their names for specific tasks. Closures, on the other hand, are agile tools available instantly without predetermined identities.

Functionality and Importance

Both functions and closures hold vital roles in Swift. Their contrasting attributes—structured names versus anonymous adaptability—set them apart. This distinction defines their essence and contributes to varied organizational convenience within the codebase.

Hope this beginner-friendly guide helps you grasp the basics of closures in Swift! Feel free to experiment and try other Swift code examples.