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