Swift Language
Verschlüsse
Suche…
Syntax
- var shutterVar: (<parameters>) -> (<returnType>) // Als Variable oder Eigenschaftstyp
- typealias ClosureType = (<parameters>) -> (<returnType>)
- {[<captureList>] (<Parameter>) <Werte> -> <Rückgabetyp> in <Anweisungen>} // Vollständige Abschlusssyntax
Bemerkungen
Weitere Informationen zu Swift-Verschlüssen finden Sie in der Dokumentation von Apple .
Grundlagen der Schließung
Verschlüsse (auch als Blöcke oder Lambdas bezeichnet ) sind Code-Teile, die in Ihrem Programm gespeichert und weitergegeben werden können.
let sayHi = { print("Hello") }
// The type of sayHi is "() -> ()", aka "() -> Void"
sayHi() // prints "Hello"
Wie andere Funktionen können auch Schließungen Argumente akzeptieren und Ergebnisse zurückgeben oder Fehler auslösen :
let addInts = { (x: Int, y: Int) -> Int in
return x + y
}
// The type of addInts is "(Int, Int) -> Int"
let result = addInts(1, 2) // result is 3
let divideInts = { (x: Int, y: Int) throws -> Int in
if y == 0 {
throw MyErrors.DivisionByZero
}
return x / y
}
// The type of divideInts is "(Int, Int) throws -> Int"
Verschlüsse können Werte aus ihrem Geltungsbereich erfassen :
// This function returns another function which returns an integer
func makeProducer(x: Int) -> (() -> Int) {
let closure = { x } // x is captured by the closure
return closure
}
// These two function calls use the exact same code,
// but each closure has captured different values.
let three = makeProducer(3)
let four = makeProducer(4)
three() // returns 3
four() // returns 4
Verschlüsse können direkt an Funktionen übergeben werden:
let squares = (1...10).map({ $0 * $0 }) // returns [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
let squares = (1...10).map { $0 * $0 }
NSURLSession.sharedSession().dataTaskWithURL(myURL,
completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) in
if let data = data {
print("Request succeeded, data: \(data)")
} else {
print("Request failed: \(error)")
}
}).resume()
Syntaxvariationen
Die grundlegende Schließsyntax lautet
{
[
Erfassungsliste]
(
Parameter)
wirft->
Rückgabetypin
Hauptteil}
.
Viele dieser Teile können weggelassen werden, daher gibt es mehrere gleichwertige Möglichkeiten, einfache Verschlüsse zu schreiben:
let addOne = { [] (x: Int) -> Int in return x + 1 }
let addOne = { [] (x: Int) -> Int in x + 1 }
let addOne = { (x: Int) -> Int in x + 1 }
let addOne = { x -> Int in x + 1 }
let addOne = { x in x + 1 }
let addOne = { $0 + 1 }
let addOneOrThrow = { [] (x: Int) throws -> Int in return x + 1 }
let addOneOrThrow = { [] (x: Int) throws -> Int in x + 1 }
let addOneOrThrow = { (x: Int) throws -> Int in x + 1 }
let addOneOrThrow = { x throws -> Int in x + 1 }
let addOneOrThrow = { x throws in x + 1 }
- Die Aufnahmeliste kann weggelassen werden, wenn sie leer ist.
- Parameter benötigen keine Typanmerkungen, wenn ihre Typen abgeleitet werden können.
- Der Rückgabetyp muss nicht angegeben werden, wenn er abgeleitet werden kann.
- Parameter müssen nicht benannt werden; stattdessen können sie mit
$0
,$1
,$2
usw. referenziert werden. - Wenn der Abschluss einen einzelnen Ausdruck enthält, dessen Wert zurückgegeben werden soll, kann das Schlüsselwort
return
weggelassen werden. - Wenn die Schließung auf einen Fehler zurückzuführen ist, in einen Kontext geschrieben wird, der eine Schließung erwartet oder keinen Fehler
throws
könnenthrows
weggelassen werden.
// The closure's type is unknown, so we have to specify the type of x and y.
// The output type is inferred to be Int, because the + operator for Ints returns Int.
let addInts = { (x: Int, y: Int) in x + y }
// The closure's type is specified, so we can omit the parameters' type annotations.
let addInts: (Int, Int) -> Int = { x, y in x + y }
let addInts: (Int, Int) -> Int = { $0 + $1 }
Schließungen an Funktionen übergeben
Funktionen können Schließungen (oder andere Funktionen) als Parameter akzeptieren:
func foo(value: Double, block: () -> Void) { ... }
func foo(value: Double, block: Int -> Int) { ... }
func foo(value: Double, block: (Int, Int) -> String) { ... }
Nachlaufende Syntax
Wenn der letzte Parameter einer Funktion ein Abschluss ist, können die Abschlussklammern {
/ }
nach dem Funktionsaufruf geschrieben werden:
foo(3.5, block: { print("Hello") })
foo(3.5) { print("Hello") }
dispatch_async(dispatch_get_main_queue(), {
print("Hello from the main queue")
})
dispatch_async(dispatch_get_main_queue()) {
print("Hello from the main queue")
}
Wenn das einzige Argument einer Funktion ein Abschluss ist, können Sie das Klammerpaar ()
auch weglassen, wenn Sie es mit der nachlaufenden Abschlusssyntax aufrufen:
func bar(block: () -> Void) { ... }
bar() { print("Hello") }
bar { print("Hello") }
@noescape
Parameter
Abschlussparameter, die mit @noescape
gekennzeichnet sind, werden garantiert ausgeführt, bevor der Funktionsaufruf zurückkehrt, also self.
ist nicht im Verschlusskörper erforderlich:
func executeNow(@noescape block: () -> Void) {
// Since `block` is @noescape, it's illegal to store it to an external variable.
// We can only call it right here.
block()
}
func executeLater(block: () -> Void) {
dispatch_async(dispatch_get_main_queue()) {
// Some time in the future...
block()
}
}
class MyClass {
var x = 0
func showExamples() {
// error: reference to property 'x' in closure requires explicit 'self.' to make capture semantics explicit
executeLater { x = 1 }
executeLater { self.x = 2 } // ok, the closure explicitly captures self
// Here "self." is not required, because executeNow() takes a @noescape block.
executeNow { x = 3 }
// Again, self. is not required, because map() uses @noescape.
[1, 2, 3].map { $0 + x }
}
}
Swift 3 Hinweis:
Beachten Sie, dass Sie in Swift 3 keine Blöcke mehr als @noescape markieren. Blöcke werden jetzt standardmäßig nicht mit Escapezeichen versehen. In Swift 3 markieren Sie einen Funktionsparameter, bei dem es sich um einen Escape-Verschluss handelt, anstatt einen Abschluss als "Nicht-Escape" zu kennzeichnen.
throws
und rethrows
Verschlüsse können wie andere Funktionen Fehler verursachen :
func executeNowOrIgnoreError(block: () throws -> Void) {
do {
try block()
} catch {
print("error: \(error)")
}
}
Die Funktion kann den Fehler natürlich an den Aufrufer weiterleiten:
func executeNowOrThrow(block: () throws -> Void) throws {
try block()
}
Wenn der übergebene Block jedoch nicht wirft, bleibt der Aufrufer mit einer Wurffunktion hängen:
// It's annoying that this requires "try", because "print()" can't throw!
try executeNowOrThrow { print("Just printing, no errors here!") }
Die Lösung heißt erneut rethrows
, was rethrows
, dass die Funktion nur werfen kann, wenn der schließende Parameter ausgelöst wird :
func executeNowOrRethrow(block: () throws -> Void) rethrows {
try block()
}
// "try" is not required here, because the block can't throw an error.
executeNowOrRethrow { print("No errors are thrown from this closure") }
// This block can throw an error, so "try" is required.
try executeNowOrRethrow { throw MyError.Example }
Viele Standardbibliotheksfunktionen verwenden rethrows
, einschließlich map()
, filter()
und indexOf()
.
Captures, starke / schwache Referenzen und Aufbewahrungszyklen
class MyClass {
func sayHi() { print("Hello") }
deinit { print("Goodbye") }
}
Wenn ein Abschluss einen Referenztyp (eine Klasseninstanz) erfasst, enthält er standardmäßig eine starke Referenz:
let closure: () -> Void
do {
let obj = MyClass()
// Captures a strong reference to `obj`: the object will be kept alive
// as long as the closure itself is alive.
closure = { obj.sayHi() }
closure() // The object is still alive; prints "Hello"
} // obj goes out of scope
closure() // The object is still alive; prints "Hello"
Die Erfassungsliste des Abschlusses kann verwendet werden, um eine schwache oder nicht besetzte Referenz anzugeben:
let closure: () -> Void
do {
let obj = MyClass()
// Captures a weak reference to `obj`: the closure will not keep the object alive;
// the object becomes optional inside the closure.
closure = { [weak obj] in obj?.sayHi() }
closure() // The object is still alive; prints "Hello"
} // obj goes out of scope and is deallocated; prints "Goodbye"
closure() // `obj` is nil from inside the closure; this does not print anything.
let closure: () -> Void
do {
let obj = MyClass()
// Captures an unowned reference to `obj`: the closure will not keep the object alive;
// the object is always assumed to be accessible while the closure is alive.
closure = { [unowned obj] in obj.sayHi() }
closure() // The object is still alive; prints "Hello"
} // obj goes out of scope and is deallocated; prints "Goodbye"
closure() // crash! obj is being accessed after it's deallocated.
Weitere Informationen finden Sie im Thema Speicherverwaltung und im Abschnitt Automatische Referenzzählung der Programmiersprache Swift.
Zyklen behalten
Wenn sich ein Objekt an einem Abschluss festhält, der auch einen starken Bezug zum Objekt hat, ist dies ein Haltezyklus . Wenn der Zyklus nicht unterbrochen wird, wird der Speicher, der das Objekt und die Schließung speichert, durchgesickert (wird nie wiederhergestellt).
class Game {
var score = 0
let controller: GCController
init(controller: GCController) {
self.controller = controller
// BAD: the block captures self strongly, but self holds the controller
// (and thus the block) strongly, which is a cycle.
self.controller.controllerPausedHandler = {
let curScore = self.score
print("Pause button pressed; current score: \(curScore)")
}
// SOLUTION: use `weak self` to break the cycle.
self.controller.controllerPausedHandler = { [weak self] in
guard let strongSelf = self else { return }
let curScore = strongSelf.score
print("Pause button pressed; current score: \(curScore)")
}
}
}
Verwenden von Verschlüssen für die asynchrone Codierung
Closures werden häufig für asynchrone Aufgaben verwendet, beispielsweise beim Abrufen von Daten von einer Website.
func getData(urlString: String, callback: (result: NSData?) -> Void) {
// Turn the URL string into an NSURLRequest.
guard let url = NSURL(string: urlString) else { return }
let request = NSURLRequest(URL: url)
// Asynchronously fetch data from the given URL.
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {(data: NSData?, response: NSURLResponse?, error: NSError?) in
// We now have the NSData response from the website.
// We can get it "out" of the function by using the callback
// that was passed to this function as a parameter.
callback(result: data)
}
task.resume()
}
Diese Funktion ist asynchron und blockiert daher nicht den Thread, für den sie aufgerufen wird (sie friert die Schnittstelle nicht ein, wenn sie im Haupt-Thread Ihrer GUI-Anwendung aufgerufen wird).
print("1. Going to call getData")
getData("http://www.example.com") {(result: NSData?) -> Void in
// Called when the data from http://www.example.com has been fetched.
print("2. Fetched data")
}
print("3. Called getData")
Da die Task asynchron ist, sieht die Ausgabe normalerweise so aus:
"1. Going to call getData"
"3. Called getData"
"2. Fetched data"
Da der Code innerhalb des Abschlusses print("2. Fetched data")
wird, wird er erst aufgerufen, wenn die Daten von der URL abgerufen werden.
Verschlüsse und Typalias
Ein Abschluss kann mit einem typealias
definiert werden. Dies stellt einen bequemen Platzhalter bereit, wenn dieselbe Verschlusssignatur an mehreren Stellen verwendet wird. Beispielsweise stellen häufige Callbacks für Netzwerkanfragen oder Ereignishandler für Benutzeroberflächen große Kandidaten dar, um mit einem Typalias "benannt" zu werden.
public typealias ClosureType = (x: Int, y: Int) -> Int
Sie können dann eine Funktion mit den Typealias definieren:
public func closureFunction(closure: ClosureType) {
let z = closure(1, 2)
}
closureFunction() { (x: Int, y: Int) -> Int in return x + y }