Swift Language
Fermetures
Recherche…
Syntaxe
- var closureVar: (<paramètres>) -> (<returnType>) // En tant que variable ou type de propriété
- typealias ClosureType = (<paramètres>) -> (<returnType>)
- {[<captureList>] (<paramètres>) <throws-ness> -> <returnType> dans <instructions>} // Syntaxe de fermeture complète
Remarques
Pour plus d'informations sur les fermetures Swift, consultez la documentation Apple .
Bases de fermeture
Les fermetures (également appelées blocs ou lambdas ) sont des morceaux de code qui peuvent être stockés et transmis dans votre programme.
let sayHi = { print("Hello") }
// The type of sayHi is "() -> ()", aka "() -> Void"
sayHi() // prints "Hello"
Comme les autres fonctions, les fermetures peuvent accepter des arguments et des résultats de retour ou des erreurs de lancement:
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"
Les fermetures peuvent capturer des valeurs de leur portée:
// 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
Les fermetures peuvent être passées directement dans les fonctions:
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()
Variations de syntaxe
La syntaxe de fermeture de base est
{
[
liste de capture]
(
paramètres)
throws-ness->
type de retourin
corps}
.
Beaucoup de ces parties peuvent être omises, il existe donc plusieurs manières équivalentes d'écrire des fermetures simples:
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 }
- La liste de capture peut être omise si elle est vide.
- Les paramètres n'ont pas besoin d'annotations de type si leurs types peuvent être déduits.
- Le type de retour n'a pas besoin d'être spécifié s'il peut être déduit.
- Les paramètres ne doivent pas être nommés; au lieu de cela, ils peuvent être référés avec
$0
$1
$2
, etc. - Si la fermeture contient une seule expression, dont la valeur doit être renvoyée, le mot-clé de
return
peut être omis. - Si la fermeture est supposée générer une erreur, est écrite dans un contexte qui attend une fermeture de lancement ou ne
throws
pas d'erreur, lesthrows
peuvent être omis.
// 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 }
Passer des fermetures dans des fonctions
Les fonctions peuvent accepter des fermetures (ou d'autres fonctions) en tant que paramètres:
func foo(value: Double, block: () -> Void) { ... }
func foo(value: Double, block: Int -> Int) { ... }
func foo(value: Double, block: (Int, Int) -> String) { ... }
Syntaxe de clôture
Si le dernier paramètre d'une fonction est une fermeture, les accolades {
/ }
peuvent être écrites après l'appel de la fonction:
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")
}
Si le seul argument d'une fonction est une fermeture, vous pouvez également omettre la paire de parenthèses ()
lorsque vous l'appelez avec la syntaxe de clôture:
func bar(block: () -> Void) { ... }
bar() { print("Hello") }
bar { print("Hello") }
Paramètres @noescape
Les paramètres de fermeture marqués @noescape
sont garantis pour s'exécuter avant le retour de l'appel de la fonction, donc en utilisant self.
n'est pas nécessaire à l'intérieur du corps de fermeture:
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 note:
Notez que dans Swift 3, vous ne marquez plus les blocs comme @noescape. Les blocs ne s'échappent plus par défaut. Dans Swift 3, au lieu de marquer une fermeture comme ne s'échappant pas, vous marquez un paramètre de fonction qui est une clôture d'échappement en évitant d'utiliser le mot clé "@escaping".
throws
et rethrows
Les fermetures, comme les autres fonctions, peuvent générer des erreurs :
func executeNowOrIgnoreError(block: () throws -> Void) {
do {
try block()
} catch {
print("error: \(error)")
}
}
La fonction peut bien entendu transmettre l’erreur à son correspondant:
func executeNowOrThrow(block: () throws -> Void) throws {
try block()
}
Cependant, si le bloc transmis ne lance pas , l'appelant est toujours bloqué par une fonction de lancement:
// It's annoying that this requires "try", because "print()" can't throw!
try executeNowOrThrow { print("Just printing, no errors here!") }
La solution est rethrows
, qui rethrows
que la fonction ne peut lancer que si son paramètre de fermeture lance :
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 }
De nombreuses fonctions de bibliothèque standard utilisent des rethrows
, notamment map()
, filter()
et indexOf()
.
Captures, références fortes / faibles et cycles de conservation
class MyClass {
func sayHi() { print("Hello") }
deinit { print("Goodbye") }
}
Lorsqu'une fermeture capture un type de référence (une instance de classe), elle contient une référence forte par défaut:
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"
La liste de capture de la fermeture peut être utilisée pour spécifier une référence faible ou non:
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.
Pour plus d'informations, reportez-vous à la rubrique Gestion de la mémoire et à la section Comptage automatique des références du langage de programmation Swift.
Conserver les cycles
Si un objet retient une fermeture, qui contient également une référence forte à l'objet, il s'agit d'un cycle de conservation . À moins que le cycle ne soit rompu, la mémoire stockant l'objet et la fermeture sera perdue (jamais récupérée).
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)")
}
}
}
Utilisation de fermetures pour le codage asynchrone
Les fermetures sont souvent utilisées pour des tâches asynchrones, par exemple lors de l'extraction de données à partir d'un site Web.
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()
}
Cette fonction est asynchrone, elle ne bloquera donc pas le thread sur lequel elle est appelée (elle ne gèlera pas l'interface si elle est appelée sur le thread principal de votre application GUI).
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")
Comme la tâche est asynchrone, la sortie ressemblera généralement à ceci:
"1. Going to call getData"
"3. Called getData"
"2. Fetched data"
Étant donné que le code à l'intérieur de la fermeture, print("2. Fetched data")
, ne sera pas appelé tant que les données de l'URL ne sont pas extraites.
Fermetures et alias de type
Une fermeture peut être définie avec une typealias
. Cela fournit un espace réservé de type pratique si la même signature de fermeture est utilisée à plusieurs endroits. Par exemple, les rappels de requêtes réseau courants ou les gestionnaires d'événements d'interface utilisateur sont d'excellents candidats pour être "nommés" avec un alias de type.
public typealias ClosureType = (x: Int, y: Int) -> Int
Vous pouvez ensuite définir une fonction en utilisant les typealias:
public func closureFunction(closure: ClosureType) {
let z = closure(1, 2)
}
closureFunction() { (x: Int, y: Int) -> Int in return x + y }