Lesson 45Closures
Escaping Closures
Escaping vs Non-Escaping
A closure "escapes" when it's called after the function returns. Use @escaping to allow this!
Comparison
Non-Escaping (default)
Called during function execution
Example: map, filter, sorted
@escaping
Called after function returns
Example: async callbacks, stored
When to Use @escaping
- Storing closure in a property or array
- Async operations (network, timers)
- Completion handlers
- Passing to another escaping closure
Using self in Escaping Closures
Must explicitly use self in escaping closures. Use [weak self] to avoid retain cycles!
Common Pattern
func fetch(completion: @escaping (Result) -> Void) {
// async work...
completion(result)
}
// async work...
completion(result)
}
main.swift
// ESCAPING vs NON-ESCAPING CLOSURES
// NON-ESCAPING (default)
// Closure is called BEFORE the function returns
func doMath(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b) // Called immediately
}
let sum = doMath(5, 3) { $0 + $1 }
print(sum) // 8
// ESCAPING CLOSURES
// Closure is called AFTER the function returns
// Must be marked with @escaping
var completionHandlers: [() -> Void] = []
func addHandler(handler: @escaping () -> Void) {
completionHandlers.append(handler) // Stored for later
}
addHandler { print("Handler 1 called") }
addHandler { print("Handler 2 called") }
// Call them later
completionHandlers[0]() // Handler 1 called
completionHandlers[1]() // Handler 2 called
// ASYNC OPERATIONS (most common use)
func fetchData(completion: @escaping (String) -> Void) {
// Simulate network delay
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// This closure runs AFTER fetchData returns
completion("Data loaded!")
}
}
print("Starting fetch...")
fetchData { result in
print(result) // Prints after 1 second
}
print("Fetch started (but not completed)")
// OUTPUT:
// Starting fetch...
// Fetch started (but not completed)
// Data loaded! (after 1 second)
// WITH CLASSES - must use self explicitly
class DataManager {
var data: String = ""
func loadData(completion: @escaping (String) -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.data = "Loaded" // Must use 'self'
completion(self.data)
}
}
}
// WEAK SELF TO AVOID RETAIN CYCLES
class ViewController {
var name = "Main"
func fetchData() {
fetchData { [weak self] result in
// 'self' might be nil if ViewController was deallocated
guard let self = self else { return }
print("\(self.name): \(result)")
}
}
}
// WHEN TO USE @escaping
// 1. Storing closure in a property
// 2. Async operations (network, timers)
// 3. Passing to another escaping closure
// 4. Completion handlersTry It Yourself!
Create a function that stores a closure and calls it later. Remember to use @escaping!