Closures
Definition
Closures sind selbstständige Funktionsblöcke, die in Code weitergegeben und verwendet werden können. Closures in Swift sind vergleichbar mit Lambda-Ausdrücken in anderen Programmiersprachen. Sie können Werte aus ihrem umgebenden Kontext "einfangen" und speichern.
Drei Formen von Closures:
- Globale Funktionen: Haben einen Namen, erfassen keine Werte
- Nested Functions: Haben einen Namen, erfassen Werte aus der umgebenden Funktion
- Closure Expressions: Unbenannte Closures mit kompakter Syntax
Basic Syntax
// Vollständige Closure-Syntax
let closure = { (parameters) -> ReturnType in
// Code
return value
}
// Einfaches Beispiel
let sayHello = { () -> Void in
print("Hello, World!")
}
sayHello() // Output: Hello, World!
Closures mit Parametern
// Closure mit einem Parameter
let greet = { (name: String) -> Void in
print("Hello, \(name)!")
}
greet("Max") // Output: Hello, Max!
// Closure mit mehreren Parametern
let add = { (a: Int, b: Int) -> Int in
return a + b
}
let result = add(5, 3) // result = 8
// Closure mit Return-Wert
let multiply = { (a: Int, b: Int) -> Int in
return a * b
}
print(multiply(4, 5)) // Output: 20
Closures als Funktionsparameter
// Funktion, die eine Closure akzeptiert
func performOperation(_ a: Int, _ b: Int, operation: (Int, Int) -> Int) -> Int {
return operation(a, b)
}
// Verwendung
let sum = performOperation(10, 5, operation: { (a: Int, b: Int) -> Int in
return a + b
})
print(sum) // Output: 15
// Mit Multiplikation
let product = performOperation(10, 5, operation: { (a: Int, b: Int) -> Int in
return a * b
})
print(product) // Output: 50
Type Inference bei Closures
// Vollständige Syntax
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map({ (number: Int) -> Int in
return number * 2
})
// Type Inference - Typen werden automatisch erkannt
let doubled2 = numbers.map({ number in
return number * 2
})
// Noch kürzer - Implizites Return
let doubled3 = numbers.map({ number in number * 2 })
// Kurzform mit $0, $1, etc.
let doubled4 = numbers.map({ $0 * 2 })
// Trailing Closure Syntax (siehe separates Dokument)
let doubled5 = numbers.map { $0 * 2 }
Capturing Values
// Closure erfasst Werte aus dem umgebenden Kontext
func makeIncrementer(incrementAmount: Int) -> () -> Int {
var total = 0
let incrementer: () -> Int = {
total += incrementAmount
return total
}
return incrementer
}
let incrementByTwo = makeIncrementer(incrementAmount: 2)
print(incrementByTwo()) // Output: 2
print(incrementByTwo()) // Output: 4
print(incrementByTwo()) // Output: 6
let incrementByFive = makeIncrementer(incrementAmount: 5)
print(incrementByFive()) // Output: 5
print(incrementByFive()) // Output: 10
Escaping Closures
// Closure wird außerhalb der Funktion ausgeführt
var completionHandlers: [() -> Void] = []
func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
completionHandlers.append(completionHandler)
}
// Typisches Beispiel: Netzwerk-Request
func fetchData(completion: @escaping (String) -> Void) {
DispatchQueue.global().async {
// Simuliere Netzwerk-Verzögerung
sleep(2)
let data = "Downloaded Data"
DispatchQueue.main.async {
completion(data)
}
}
}
// Verwendung
fetchData { result in
print("Received: \(result)")
}
Non-Escaping Closures (Standard)
// Standard: Closure wird innerhalb der Funktion ausgeführt
func performTask(action: () -> Void) {
print("Start")
action()
print("End")
}
performTask {
print("Performing action")
}
// Output:
// Start
// Performing action
// End
Autoclosures
// @autoclosure verzögert die Auswertung
func logIfTrue(_ condition: @autoclosure () -> Bool, message: String) {
if condition() {
print(message)
}
}
// Verwendung - sieht aus wie ein normaler Parameter
var userIsAuthenticated = true
logIfTrue(userIsAuthenticated, message: "User is logged in")
// Ohne @autoclosure müsste man schreiben:
// logIfTrue({ userIsAuthenticated }, message: "User is logged in")
Closures in Collections
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// map - transformiert jedes Element
let squared = numbers.map { $0 * $0 }
print(squared) // [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
// filter - filtert Elemente
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // [2, 4, 6, 8, 10]
// reduce - reduziert auf einen Wert
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // 55
// sorted - sortiert Elemente
let descending = numbers.sorted { $0 > $1 }
print(descending) // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
// compactMap - filtert nil-Werte
let strings = ["1", "2", "three", "4", "5"]
let validNumbers = strings.compactMap { Int($0) }
print(validNumbers) // [1, 2, 4, 5]
// forEach - führt Aktion für jedes Element aus
numbers.forEach { print($0) }
Praktische Beispiele
Button Action in SwiftUI
Button("Tap Me") {
print("Button wurde gedrückt")
}
Animation Completion
withAnimation {
isVisible.toggle()
} completion: {
print("Animation abgeschlossen")
}
Sortieren mit Custom Logic
struct Person {
let name: String
let age: Int
}
let people = [
Person(name: "Anna", age: 25),
Person(name: "Bob", age: 30),
Person(name: "Charlie", age: 20)
]
// Nach Alter sortieren
let sortedByAge = people.sorted { $0.age < $1.age }
// Nach Namen sortieren
let sortedByName = people.sorted { $0.name < $1.name }
Netzwerk-Callback
class NetworkManager {
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
if let data = data {
do {
let user = try JSONDecoder().decode(User.self, from: data)
completion(.success(user))
} catch {
completion(.failure(error))
}
}
}.resume()
}
}
// Verwendung
networkManager.fetchUser(id: 123) { result in
switch result {
case .success(let user):
print("User: \(user.name)")
case .failure(let error):
print("Error: \(error)")
}
}
Timer mit Closure
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
print("Tick: \(Date())")
}
Custom Validation
func validate(_ value: String, rules: [(String) -> Bool]) -> Bool {
return rules.allSatisfy { rule in
rule(value)
}
}
let notEmpty: (String) -> Bool = { !$0.isEmpty }
let minLength: (String) -> Bool = { $0.count >= 6 }
let hasNumber: (String) -> Bool = { $0.contains(where: { $0.isNumber }) }
let password = "Pass123"
let isValid = validate(password, rules: [notEmpty, minLength, hasNumber])
print(isValid) // true
Weak und Unowned bei Closures
class ViewController {
var name = "Main View"
// Retain Cycle vermeiden mit [weak self]
func setupWithWeak() {
someAsyncFunction { [weak self] in
guard let self = self else { return }
print(self.name)
}
}
// Mit unowned (nur wenn self garantiert existiert)
func setupWithUnowned() {
someAsyncFunction { [unowned self] in
print(self.name)
}
}
// Mehrere captured Werte
func setupWithMultipleCaptures() {
let manager = NetworkManager()
someAsyncFunction { [weak self, weak manager] in
guard let self = self, let manager = manager else { return }
print("\(self.name) - \(manager.status)")
}
}
}
Best Practices
- Verwende [weak self] bei Closures in Klassen: Verhindert Retain Cycles
- Nutze Trailing Closure Syntax: Macht Code lesbarer
- Verwende Shorthand-Argumente (1): Für kurze, einfache Closures
- @escaping explizit markieren: Wenn Closure nach Funktionsende verwendet wird
- Type Inference nutzen: Reduziert Boilerplate-Code
- Kompakte Syntax für einfache Operationen: z.B.
map { $0 * 2 } - Aussagekräftige Namen bei komplexen Closures: Statt 1
Common Use Cases
- Event Handling: Button actions, gestures
- Asynchrone Operationen: Network requests, file I/O
- Collection Transformationen: map, filter, reduce
- Completion Handlers: Callbacks nach Operationen
- Animation Callbacks: Actions nach Animationen
- Custom Sortierung: Sortier-Logik für Collections
- Validation: Custom Validierungs-Regeln
Related Topics
- Trailing Closures: Spezielle Syntax für Closures als letzter Parameter
- Type Inference: Automatische Typ-Erkennung
- Memory Management: Weak/Unowned References
- Higher-Order Functions: map, filter, reduce