Skip to main content

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:

  1. Globale Funktionen: Haben einen Namen, erfassen keine Werte
  2. Nested Functions: Haben einen Namen, erfassen Werte aus der umgebenden Funktion
  3. 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

  1. Verwende [weak self] bei Closures in Klassen: Verhindert Retain Cycles
  2. Nutze Trailing Closure Syntax: Macht Code lesbarer
  3. Verwende Shorthand-Argumente (0,0, 1): Für kurze, einfache Closures
  4. @escaping explizit markieren: Wenn Closure nach Funktionsende verwendet wird
  5. Type Inference nutzen: Reduziert Boilerplate-Code
  6. Kompakte Syntax für einfache Operationen: z.B. map { $0 * 2 }
  7. Aussagekräftige Namen bei komplexen Closures: Statt 0,0, 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
  • 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