Swift Closures

Self-contained blocks of functionality in Swift

📦 What are Closures?

Closures are self-contained blocks of code that can be passed around and used in your programs. They're like functions but more flexible and can capture values from their surrounding context.


// Simple closure that adds two numbers
let addClosure = { (a: Int, b: Int) -> Int in
    return a + b
}

let result = addClosure(5, 3)
print("Result: \(result)")
                                    

Output:

Result: 8

Closure Concepts

🔧

Syntax

Defined with curly braces and 'in' keyword

{ (parameters) -> ReturnType in
    // code here
}
📋

Parameters

Accept input like functions

let greet = { (name: String) in
    print("Hello, \(name)!")
}
🎯

Capture Values

Access variables from surrounding scope

let multiplier = 3
let triple = { (x: Int) in x * multiplier }
🔄

Higher-Order Functions

Used with map, filter, reduce

let doubled = numbers.map { $0 * 2 }

🔹 Basic Closure Syntax

Closures have a flexible syntax that can be simplified:

// Full closure syntax
let fullSyntax = { (name: String) -> String in
    return "Hello, \(name)!"
}

// Simplified - type inference
let simplified = { name in
    return "Hello, \(name)!"
}

// Even shorter - implicit return
let shorter = { name in "Hello, \(name)!" }

// Shortest - shorthand argument names
let shortest = { "Hello, \($0)!" }

// Test all versions
print(fullSyntax("Alice"))
print(simplified("Bob"))
print(shorter("Charlie"))
print(shortest("Diana"))

// Closure with multiple parameters
let calculator = { (a: Int, b: Int, operation: String) -> Int in
    switch operation {
    case "+": return a + b
    case "-": return a - b
    case "*": return a * b
    case "/": return a / b
    default: return 0
    }
}

print("5 + 3 = \(calculator(5, 3, "+"))")
print("10 - 4 = \(calculator(10, 4, "-"))")

Output:

Hello, Alice!

Hello, Bob!

Hello, Charlie!

Hello, Diana!

5 + 3 = 8

10 - 4 = 6

🔹 Closures with Higher-Order Functions

Closures are commonly used with array methods:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// Map - transform each element
let doubled = numbers.map { $0 * 2 }
let squared = numbers.map { $0 * $0 }
print("Doubled: \(doubled)")
print("Squared: \(squared)")

// Filter - select elements that meet criteria
let evenNumbers = numbers.filter { $0 % 2 == 0 }
let largeNumbers = numbers.filter { $0 > 5 }
print("Even numbers: \(evenNumbers)")
print("Numbers > 5: \(largeNumbers)")

// Reduce - combine all elements into single value
let sum = numbers.reduce(0) { $0 + $1 }
let product = numbers.reduce(1) { $0 * $1 }
print("Sum: \(sum)")
print("Product: \(product)")

// Chaining operations
let result = numbers
    .filter { $0 % 2 == 0 }    // Get even numbers
    .map { $0 * $0 }           // Square them
    .reduce(0) { $0 + $1 }     // Sum the squares

print("Sum of squares of even numbers: \(result)")

// Working with strings
let names = ["Alice", "Bob", "Charlie", "Diana"]
let uppercased = names.map { $0.uppercased() }
let longNames = names.filter { $0.count > 4 }
print("Uppercased: \(uppercased)")
print("Long names: \(longNames)")

Output:

Doubled: [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Squared: [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

Even numbers: [2, 4, 6, 8, 10]

Numbers > 5: [6, 7, 8, 9, 10]

Sum: 55

Product: 3628800

Sum of squares of even numbers: 220

Uppercased: ["ALICE", "BOB", "CHARLIE", "DIANA"]

Long names: ["Alice", "Charlie", "Diana"]

🔹 Capturing Values

Closures can capture and store references to variables from their surrounding context:

// Capturing variables from surrounding scope
func makeIncrementer(incrementAmount: Int) -> () -> Int {
    var total = 0
    let incrementer: () -> Int = {
        total += incrementAmount  // Captures 'total' and 'incrementAmount'
        return total
    }
    return incrementer
}

let incrementByTwo = makeIncrementer(incrementAmount: 2)
let incrementByFive = makeIncrementer(incrementAmount: 5)

print("First call: \(incrementByTwo())")   // 2
print("Second call: \(incrementByTwo())")  // 4
print("Third call: \(incrementByTwo())")   // 6

print("First call (by 5): \(incrementByFive())")  // 5
print("Second call (by 5): \(incrementByFive())") // 10

// Capturing example with array sorting
let students = ["Alice", "Bob", "Charlie", "Diana"]
var sortOrder = "ascending"

let sortedStudents = students.sorted { (name1, name2) in
    if sortOrder == "ascending" {
        return name1 < name2
    } else {
        return name1 > name2
    }
}

print("Sorted (\(sortOrder)): \(sortedStudents)")

// Change the captured variable
sortOrder = "descending"
let sortedDescending = students.sorted { (name1, name2) in
    if sortOrder == "ascending" {
        return name1 < name2
    } else {
        return name1 > name2
    }
}

print("Sorted (\(sortOrder)): \(sortedDescending)")

Output:

First call: 2

Second call: 4

Third call: 6

First call (by 5): 5

Second call (by 5): 10

Sorted (ascending): ["Alice", "Bob", "Charlie", "Diana"]

Sorted (descending): ["Diana", "Charlie", "Bob", "Alice"]

🔹 Trailing Closure Syntax

When a closure is the last parameter, you can use trailing closure syntax:

// Function that takes a closure
func performOperation(on numbers: [Int], operation: (Int) -> Int) -> [Int] {
    return numbers.map(operation)
}

// Regular syntax
let doubled1 = performOperation(on: [1, 2, 3, 4], operation: { $0 * 2 })

// Trailing closure syntax (cleaner)
let doubled2 = performOperation(on: [1, 2, 3, 4]) { $0 * 2 }

print("Doubled (regular): \(doubled1)")
print("Doubled (trailing): \(doubled2)")

// Multiple trailing closures (Swift 5.3+)
func processData(
    data: [Int],
    filter: (Int) -> Bool,
    transform: (Int) -> String
) -> [String] {
    return data.filter(filter).map(transform)
}

let result = processData(data: [1, 2, 3, 4, 5, 6]) { number in
    number % 2 == 0  // Filter for even numbers
} transform: { number in
    "Number: \(number)"  // Transform to string
}

print("Processed data: \(result)")

// Common use with animations (conceptual)
func animateView(duration: Double, animations: () -> Void, completion: () -> Void) {
    print("Starting animation for \(duration) seconds")
    animations()
    print("Animation completed")
    completion()
}

animateView(duration: 0.5) {
    print("Animating view...")
} completion: {
    print("Animation finished!")
}

Output:

Doubled (regular): [2, 4, 6, 8]

Doubled (trailing): [2, 4, 6, 8]

Processed data: ["Number: 2", "Number: 4", "Number: 6"]

Starting animation for 0.5 seconds

Animating view...

Animation completed

Animation finished!

🔹 Escaping and Non-Escaping Closures

Closures can be escaping (called after function returns) or non-escaping (called before function returns):

// Non-escaping closure (default)
func processImmediately(data: [Int], processor: (Int) -> Int) -> [Int] {
    return data.map(processor)  // Closure is called immediately
}

// Escaping closure - can be stored and called later
var storedClosures: [() -> Void] = []

func storeForLater(closure: @escaping () -> Void) {
    storedClosures.append(closure)  // Closure escapes the function
}

// Example usage
let numbers = [1, 2, 3, 4, 5]

// Non-escaping - called immediately
let processed = processImmediately(data: numbers) { $0 * 2 }
print("Processed immediately: \(processed)")

// Escaping - stored for later
storeForLater {
    print("This closure was stored and called later!")
}

storeForLater {
    print("Another stored closure!")
}

// Execute stored closures
print("Executing stored closures:")
for closure in storedClosures {
    closure()
}

// Practical example: Completion handlers
func downloadData(completion: @escaping (String) -> Void) {
    // Simulate async operation
    DispatchQueue.global().async {
        // Simulate network delay
        Thread.sleep(forTimeInterval: 1)
        let data = "Downloaded data"
        
        DispatchQueue.main.async {
            completion(data)  // Closure escapes and is called later
        }
    }
}

print("Starting download...")
downloadData { data in
    print("Received: \(data)")
}

Output:

Processed immediately: [2, 4, 6, 8, 10]

Executing stored closures:

This closure was stored and called later!

Another stored closure!

Starting download...

Received: Downloaded data

🧠 Test Your Knowledge

What keyword separates closure parameters from the body?