Closures in Swift

Bei Closures handelt es sich im Grunde um eine besondere Art von Funktionen, die aber über keinen Funktionsnamen verfügen. Es handelt sich um den Nachfolger der Blocks in Objective-C.

Closures können in unterschiedlicher Gestalt auftreten. Die Basissyntax ist dabei stets gleich: Es gibt einen oder mehrere Parameter und einen Rückgabewert.

{ (value: Int, name: String) -> Double in 
    // closure body goes here
}

Umschlossen von runden Klammern befinden sich die Parameter value und name. Nach dem Pfeil (->) folgt der Rückgabewert (hier: Double). Bei in handelt es sich um ein Schlüsselwort, wobei der Teil vor dem in der Header ist. Nach dem in folgt der Body mit dem auszuführenden Code.

Es geht aber noch simpler; bei folgendem Beispiel ist weder ein Parameter, noch ein Rückgabewert vorhanden:

{ () -> Void in
    print("I am a closure.")
}

Anstelle von Void kann auch ein leeres Klammerpaar () geschrieben werden.

{ () -> () in
    print("I am a closure.")
}

Für sich genommen ist die Syntax recht übersichtlich. Allerdings kann ein Closure in unterschiedlichem Gewande auftreten, was bisweilen etwas irritierend sein kann. Wer sich davon ein Bild machen möchte, sollte sich diese Übersicht ansehen. Diese Internetseite vermittelt außerdem eine Vorstellung von den Einsatzmöglichkeiten von Closures.

Einige (wenige) dieser Möglichkeiten möchte ich hier vorstellen.

Ein Closure als Variable

Ein Closure kann einer Variablen zugewiesen werden:

var myClosure = { () -> Void in print("Hello World!") }
// Closure aufrufen
myClosure() // -> "Hello World!"

Der Header eines Closure, hier also () -> Void, ist die Signatur des Closure. Sofern man bei der Deklaration einer Variable den Typ angeben möchte, verwendet man bei Closures hierfür die Signatur:

var myClosure: () -> () = { print("Hello Wordl!") }

Sofern weder Parameter noch ein Rückgabewert vorhanden sind, kann man den Header bzw. die Signatur auch weglassen:

var myClosure = { print("Hello World!") }

Ein Closure als Parameter einer Funktion

Eines häufiges Einsatzgebiet ist die Verwendung eines Closures als Parameter einer Funktion; dazu folgendes Beispiel:

func myFunction (_ number: Int, handler: () -> Void) {
    for _ in 0 ..< number {
        handler()
    }
}

let myClosure = { () -> Void in
    print("I am a closure.")
}

myFunction(4, handler: myClosure)

Nach Aufruf der Funktion myFunction, die neben einem Integer-Wert auch ein Closure als Parameter akzeptiert, wird hier – da myClosure als Argument übergeben wird – viermal “I am a closure.” in der Konsole ausgegeben. Denn myClosure enthält im Body eine entsprechende print-Funktion.

Anstatt eine Variable myClosure zu definieren, wäre es auch möglich, den Closure-Code inline zu verwenden. Man spricht hierbei auch von einer shorthand syntax:

myFunction(4, handler: { print("I am a closure.") })

Sofern es sich bei dem Closure um den letzten Parameter handelt, kann auch eine andere Schreibweise verwendet werden; man spricht hierbei von einem trailing closure:

myFunction(4) { print("I am a closure.") }

Um den Code übersichtlicher zu gestalten, bietet es sich daher an, ein Closure stets als letzen Parameter zu verwenden.

Ein Closure als typealias deklariert

Sofern ein Closure mit Parametern verwendet wird, bietet es sich an, ein typealias zu erstellen. Dazu das obige Beispiel in abgewandelter Form:

typealias printClosure = () -> Void

    func myFunction (_ number: Int, handler: printClosure) {
        for _ in 0 ..< number {
            handler()
        }
    }

    let myClosure = { () -> Void in
        print("I am a closure.")
    }

    myFunction(4, handler: myClosure)

In diesem Fall macht das freilich nicht viel Sinn, denn mit () -> Void liegt ein sehr simpler Closure-Code vor. Vielmehr soll hier nur das Prinzip deutlich werden.

Ein Closure mit einem Parameter

Im gezeigten Beispiel hat myClosure keinen Parameter. Das wollen wir nun ändern, wobei ich auch hier wieder ein typealias verwende:

    typealias nameClosure = (_ name: String) -> Void

    func mySecondFunction(_ number: Int, handler: nameClosure) {
        let chancellor = "Angela Merkel"
        for _ in 0 ..< number {
            handler(chancellor)
        }
    }

    let mySecondClosure = { (name: String) -> Void in
        print("\(name) is the chancellor of Germany.")
    }

    mySecondFunction(4, handler: mySecondClosure)

Ein Closure nutzt einen Platzhalter

Auch Platzhalter ($0, $1, usw.) funktionieren im Zusammenspiel mit einem Closure und erlauben so die Verkürzung des Codes:

    typealias nameClosure = (_ name: String) -> Void

    func mySecondFunction(_ number: Int, handler: nameClosure) {
        let chancellor = "Angela Merkel"
        for _ in 0 ..< number {
            handler(chancellor)
        }
    }

    let mySecondClosure = { (name: String) -> Void in
        print("$0 is the chancellor of Germany.")
    }

    mySecondFunction(4, handler: mySecondClosure)

Betrachten wir dazu ein weiteres Beispiel, bei dem ein Closure nicht ein Funktionsparameter ist, sondern einer Variable zugewiesen wird:

let myClosure: (Int, Int) -> Int = 
    { (firstNumber: Int, secondNumber: Int) in return firstNumber + secondNumber }
myClosure(10, 10) // -> 20

Dies lässt sich vereinfachen zu:

let myClosure: (Int, Int) -> Int = { return $0 + $1 }
myClosure(10, 10) // -> 20

Und da die Angabe von return nicht erforderlich ist, funktioniert auch folgender Code:

let myClosure: (Int, Int) -> Int = { $0 + $1 }

Die Capture-Eigenschaft

Ein Closure kann eine Variable, die sich außerhalb des Closures befindet, “auffangen” (capture):

var number = 1
var incrementClosure = { number += 1 }

incrementClosure()
incrementClosure()
number // -> 3

Obwohl die Variable number außerhalb des Closures definiert wird, kann sie im Closure genutzt werden.

Dazu ein weiteres Beispiel mit einem Closure als Rückgabewert einer Funktion:

    func myThirdFunction(name: String) -> ((_ party: String) -> String) {

        let closure = { (party: String) -> String in
            let myString = String("\(name) is a member of the \(party).")
            return myString!
        }
        return closure
    }

    // "myClosure" ist ein Closure, dem die Funktion "myThirdFunction" zugewiesen wird.
    let myClosure = myThirdFunction(name: "Angela Merkel")

    // Nun kann "myClosure" mit einem Argument aufgerufen werden.
    myClosure("CDU")

Zunächst einmal haben wir hier eine Funktion myThirdFunction, die ein Closure als Rückgabewert hat. Innerhalb der Funktion wird dann ein Closure der Konstante closure zugewiesen und schließlich auch mit return zurückgegeben. Wie bereits im vorangehenden Beispiel gezeigt, sind Closures in der Lage, Werte desselben Kontext “aufzufangen” (capture), also zu nutzen. Deswegen kann die innerhalb der Funktion erstellte Closure auf den Funktionsparameter name zugreifen.

Nach Beendigung der Funktion myThirdFunction dürfte dieser Funktionsparameter name nun eigentlich gar nicht mehr zur Verfügung stehen. Da dieser Parameter aber innerhalb des Closures referenziert wurde, und die Funktion ein Closure zurückgibt, steht name weiterhin zur Verfügung. Nach dem Aufruf von myClosure kann name daher genutzt werden.

Zusammengefasst lässt sich also sagen, dass Closures

  • zum einen Werte desselben Kontext nutzen können (Capture-Eigenschaft),
  • zum anderen diese Werte (Parameter) einer Funktion auch nach Beendigung der Funktion weiterhin zur Verfügung stehen.

Durch Benutzung dieser Website erklären Sie sich mit der Verwendung von Cookies einverstanden. Mehr Informationen

Die Verwendung von Cookies dient dazu, Inhalte und Anzeigen zu personalisieren, Funktionen für soziale Medien anbieten zu können und die Zugriffe auf diese Website zu analysieren. Außerdem werden Informationen zur Nutzung dieser Webseite an Partner für soziale Medien, Werbung und Analysen weitergegeben.

Schließen