Funktions-Typen und Closures
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:
-
Das return-Statement kann entfallen, wenn der Closure-Block nur aus einem einzigen Ausdruck besteht:
numbers.map( { (element : Int) -> String in "\(element) EUR" } )
-
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" } )
-
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"}
-
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, +)