Funktions-Typen und Closures

von @ralfebert · aktualisiert am 4. November 2021
Xcode 13 & Swift 5.5
Swift Kurzreferenz

Funktions-Typen

Funktionen fügen sich nahtlos in das Typsystem von Swift ein und können als Werte verwendet und übergeben werden. Nehmen wir als Ausgangspunkt eine Funktion, die einen Int-Wert als String formatiert:

func format(value: Int) -> String {
    return "\(value) EUR"
}

In Swift kann eine Referenz auf eine Funktion in einer Variable abgelegt oder als Parameter übergeben werden:

let function: (Int) -> String = format           // Referenz auf die Funktion
function(10)                                     // Rückgabewert "10 EUR"

(Int) -> String ist dabei der Typ einer Funktion, die ein Int-Parameter bekommt und einen String zurückliefert (die explizite Typdeklaration erfolgt hier nur um diesen Typ explizit zu zeigen, der Typ würde vom Compiler automatisch ermittelt werden).

Beispiel Array#map

Die Swift Standard-Library stellt eine Vielzahl von Methoden bereit, die Funktionen als Parameter übergeben bekommen. So können z.B. mit Array#map die Werte eines Arrays transformiert werden:

let numbers = [10, 20, 30]
let formattedNumbers = numbers.map(format)   // ["10 EUR", "20 EUR", "30 EUR"]

Dabei muss eine Funktion übergeben werden, die dem Element-Typ des Arrays entspricht (Int im Beispiel). Die Funktion kann einen beliebigen Wert zurückliefern und das resultierende Array entspricht wieder diesem Typ ([String] im Beispiel).

Closures

Das Prinzip der Funktions-Typen wird durch Closures ergänzt. Ein Closure ist eine „namenlose“ Funktion:

numbers.map( { (value : Int) -> String in
    return "\(value) EUR"
} )

Eine Besonderheit ist dabei, das auf außerhalb des Closure-Blocks deklarierte Variablen zugegriffen werden darf:

let currency = "EUR"
numbers.map( { (value : Int) -> String in
    return "\(value) \(currency)"
} )

Werden Werte verwendet, die außerhalb des Closure-Blocks deklariert sind, werden diese an den Block gebunden - der Block selbst könnte weitergereicht werden und den ursprünglichen Kontext verlassen. Das Binden der benötigten Werte an den Block wird als Capture bezeichnet.

Kurzschreibweisen für Closures

Neben der oben verwendeten ausführlichen Syntax können Closure-Blöcke verkürzt geschrieben werden:

  1. Das return-Statement kann entfallen, wenn der Closure-Block nur aus einem einzigen Ausdruck besteht:

    numbers.map( { (element : Int) -> String in "\(element) EUR" } )
    
  2. Die Typen der Parameter / Rückgabewerte werden vom Compiler ermittelt und die runden Klammern um die Parameterwerte können weggelassen werden:

    numbers.map( { (element) in "\(element) EUR" } )
    
  3. Wenn der letzte Parameter einer Funktion ein Block ist, kann der Block als Trailing Closurehinter den Funktionsaufruf geschrieben werden. Ist das Closure das einzige Argument, können auch die Klammern entfallen:

    numbers.map() { element in "\(element) EUR"}
    numbers.map{ element in "\(element) EUR"}
    
  4. Parameterwerte können ohne explizite Deklaration über $0, $1, $2, ... verwendet werden:

    numbers.map { "\($0) EUR" }
    

Blockbasierte Listenoperationen

Die Swift Standard Library stellt eine Vielzahl von praktischen Methoden bereit, die einen Block als Parameter erwarten:

Filtern

let numbers = [10, 20, 30]
numbers.filter { element in element > 10 }       // [20, 30]

Transformieren

Die Array-Funktion map ruft eine Funktion für jedes Listenelement auf und liefert ein Array mit den zurückgegebenen Werten:

numbers.map { element in "\(element) EUR" }      // Ergebnis: ["10 EUR", "20 EUR", "30 EUR"]

Sortieren

struct Person {
    var name: String
    var age: Int
}

let persons = [
    Person(name: "Bob", age: 20),
    Person(name: "Carol", age: 30),
    Person(name: "Alice", age: 10)
]
let sortedPersons = persons.sorted { $0.name < $1.name }

Summenbildung

persons.reduce(0) { sum, person in sum + person.age }

Dies lässt sich wiederum nochmals verkürzen, da der + Operator ebenfalls eine Funktion ist, die übergeben werden kann. Zudem lässt sich, wenn eine Funktion benötigt wird, die auf eine Eigenschaft zugreift, dies als Keypath schreiben:

persons.map { $0.age }.reduce(0, +)
persons.map(\.age).reduce(0, +)

Weitere Informationen