Swift Language
sluitingen
Zoeken…
Syntaxis
- var closingVar: (<parameters>) -> (<returnType>) // Als een variabele of eigenschapstype
- typealias ClosureType = (<parameters>) -> (<returnType>)
- {[<captureList>] (<parameters>) <throws-ness> -> <returnType> in <statements>} // Volledige syntaxis van de sluiting
Opmerkingen
Raadpleeg de documentatie van Apple voor meer informatie over Swift-sluitingen.
Grondbeginselen van de sluiting
Sluitingen (ook bekend als blokken of lambdas ) zijn stukjes code die kunnen worden opgeslagen en doorgegeven binnen uw programma.
let sayHi = { print("Hello") }
// The type of sayHi is "() -> ()", aka "() -> Void"
sayHi() // prints "Hello"
Net als andere functies kunnen sluitingen argumenten accepteren en resultaten retourneren of fouten veroorzaken :
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"
Bij sluitingen kunnen waarden uit hun bereik worden vastgelegd :
// 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
Sluitingen kunnen direct in functies worden doorgegeven:
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()
Syntaxisvariaties
De standaard syntaxis van de sluiting is
{
[
opnamelijst]
(
parameters)
gooi-heid->
retourtypein
body}
.
Veel van deze onderdelen kunnen worden weggelaten, dus er zijn verschillende gelijkwaardige manieren om eenvoudige sluitingen te schrijven:
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 }
- De opnamelijst kan worden weggelaten als deze leeg is.
- Parameters hebben geen type-annotaties nodig als hun typen kunnen worden afgeleid.
- Het retourtype hoeft niet te worden opgegeven als het kan worden afgeleid.
- Parameters hoeven niet benoemd te worden; in plaats daarvan kunnen ze worden aangeduid met
$0
,$1
,$2
, enz. - Als de sluiting een uitdrukking, waarvan de waarde moet worden geretourneerd bevat, het
return
kan sleutelwoord worden weggelaten. - Als de sluiting wordt afgeleid om een fout te gooien, is geschreven in een context die een gooiende sluiting verwacht, of geen fout
throws
kunnenthrows
worden weggelaten.
// 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 }
Sluitingen doorgeven aan functies
Functies kunnen sluitingen (of andere functies) als parameters accepteren:
func foo(value: Double, block: () -> Void) { ... }
func foo(value: Double, block: Int -> Int) { ... }
func foo(value: Double, block: (Int, Int) -> String) { ... }
Syntaxis van de afsluitende sluiting
Als de laatste parameter van een functie een sluiting is, kunnen de sluiting accolades {
/ }
worden geschreven na de functieoproep:
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")
}
Als het enige argument van een functie een afsluiting is, kunt u ook het paar haakjes ()
weglaten wanneer u het aanroept met de syntaxis van de afsluitende afsluiting:
func bar(block: () -> Void) { ... }
bar() { print("Hello") }
bar { print("Hello") }
@noescape
parameters
Sluitingsparameters gemarkeerd @noescape
worden gegarandeerd uitgevoerd voordat de functieaanroep terugkeert, dus met self.
is niet vereist in het afsluitlichaam:
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 opmerking:
Merk op dat je in Swift 3 blokken niet langer markeert als @noescape. Blokken ontsnappen nu niet standaard. In Swift 3 markeert u in plaats van een sluiting te markeren als niet-escaping, een functieparameter die een escaping-sluiting is als escaping met het trefwoord "@escaping".
throws
en rethrows
Sluitingen kunnen, net als andere functies, fouten veroorzaken :
func executeNowOrIgnoreError(block: () throws -> Void) {
do {
try block()
} catch {
print("error: \(error)")
}
}
De functie kan de fout natuurlijk doorgeven aan de beller:
func executeNowOrThrow(block: () throws -> Void) throws {
try block()
}
Als het ingevoerde blok echter niet werpt, zit de beller nog steeds vast met een werpfunctie:
// It's annoying that this requires "try", because "print()" can't throw!
try executeNowOrThrow { print("Just printing, no errors here!") }
De oplossing is rethrows
, wat rethrows
dat de functie alleen kan werpen als zijn sluitingsparameter gooit :
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 }
Veel standaard bibliotheekfuncties gebruiken rethrows
, inclusief map()
, filter()
en indexOf()
.
Legt vast, sterke / zwakke referenties en bewaart cycli
class MyClass {
func sayHi() { print("Hello") }
deinit { print("Goodbye") }
}
Wanneer een sluiting een referentietype (een klasse-instantie) vastlegt, bevat deze standaard een sterke referentie:
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"
Capture lijst De sluiting kan worden gebruikt om een zwakke of unowned referentie op te geven:
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.
Zie het onderwerp Geheugenbeheer en het gedeelte Automatische referentietelling van de programmeertaal Swift voor meer informatie.
Bewaar cycli
Als een object een sluiting vasthoudt, die ook een sterke verwijzing naar het object bevat, is dit een vasthoudcyclus . Tenzij de cyclus wordt verbroken, zal het geheugen waarin het object en de sluiting zijn opgeslagen, worden gelekt (nooit teruggevorderd).
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)")
}
}
}
Sluitingen gebruiken voor asynchrone codering
Sluitingen worden vaak gebruikt voor asynchrone taken, bijvoorbeeld bij het ophalen van gegevens van een 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()
}
Deze functie is asynchroon, dus blokkeert de thread niet die wordt gebruikt (de interface wordt niet bevroren als deze wordt aangeroepen via de hoofdthread van uw GUI-toepassing).
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")
Omdat de taak asynchroon is, ziet de uitvoer er meestal als volgt uit:
"1. Going to call getData"
"3. Called getData"
"2. Fetched data"
Omdat de code in de afsluiting, print("2. Fetched data")
, niet wordt aangeroepen totdat de gegevens uit de URL zijn opgehaald.
Sluitingen en Type Alias
Een sluiting kan worden gedefinieerd met een typealias
. Dit biedt een handige plaatsaanduiding als dezelfde sluitingshandtekening op meerdere plaatsen wordt gebruikt. Gebruikelijke callbacks voor netwerkverzoeken of gebruikersinterface-eventhandlers zijn bijvoorbeeld uitstekende kandidaten om met een type-alias 'benoemd' te worden.
public typealias ClosureType = (x: Int, y: Int) -> Int
U kunt vervolgens een functie definiëren met behulp van de typealias:
public func closureFunction(closure: ClosureType) {
let z = closure(1, 2)
}
closureFunction() { (x: Int, y: Int) -> Int in return x + y }