() translation by (you can also view the original English article)
Si vous avez travaillé avec des blocs dans C/Objective-C ou lambdas à Ruby, vous n'aurez pas du mal à occuper votre tête autour du concept de fermetures. Les fermetures ne sont que des blocs de fonctionnalités que vous pouvez transmettre dans votre code.
En fait, nous avons déjà travaillé avec des fermetures dans les deux articles précédents. C'est vrai. Les fonctions sont également des fermetures. Commençons par les bases et inspectons l'anatomie d'une fermeture.
1. Qu'est-ce qu'une fermeture?
Comme je l'ai dit, une fermeture est un bloc de fonctionnalités que vous pouvez transmettre dans votre code. Vous pouvez passer une fermeture comme argument d'une fonction ou vous pouvez la stocker comme une propriété d'un objet. Les fermetures ont de nombreux cas d'utilisation.
La fermeture du nom fait ressortir l'une des principales caractéristiques des fermetures. Une fermeture capture les variables et les constantes du contexte dans lequel elles sont définies. On parle parfois de la fermeture de ces variables et constantes. Nous allons voir la valeur saisie plus en détail à la fin de cet article.
La flexibilité
Vous avez déjà appris que les fonctions peuvent être incroyablement puissantes et flexibles. Parce que les fonctions sont des fermetures, les fermetures sont aussi flexibles. Dans cet article, vous découvrirez à quel point ils sont flexibles et puissants.
Gestion de la mémoire
Le langage de programmation C a un concept similaire, des blocs. Les fermetures dans Swift, cependant, ont quelques avantages. L'un des principaux avantages des fermetures dans Swift est que la gestion de la mémoire est quelque chose que vous, le développeur, ne vous inquiétez pas.
Même les cycles de conservation, qui ne sont pas courants dans C/Objective-C, sont traités par Swift. Cela réduira les fuites ou les pertes de mémoire difficiles à trouver en raison de pointeurs non valides.
2. Syntaxe
La syntaxe de base d'une fermeture n'est pas difficile et elle vous rappellera des fonctions globales et imbriquées, que nous avons abordées plus tôt dans cette série. Jetez un œil à l'exemple suivant.
1 |
{(a: Int) -> Int in |
2 |
return a + 1 |
3 |
}
|
La première chose que vous remarquerez, c'est que toute la fermeture est enveloppée dans une paire d'accolades. Les paramètres de fermeture sont enveloppés dans une paire de parenthèses, séparés du type de retour par le symbole ->
. La fermeture ci-dessus accepte un argument, a
, de type Int
et renvoie un Int
. Le corps de la fermeture commence après le mot-clé in
.
Les fermetures nommées, c'est-à-dire les fonctions globales et imbriquées, semblent un peu différentes. L'exemple suivant devrait illustrer les différences.
1 |
func increment(a: Int) -> Int { |
2 |
return a + 1 |
3 |
}
|
Les différences les plus importantes sont l'utilisation du mot-clé func
et la position des paramètres et du type de retour. Une fermeture commence et se termine par un accolade, enroulant les paramètres, le type de retour et le corps de fermeture. Malgré ces différences, n'oubliez pas que chaque fonction est une fermeture. Cependant, toutes les fermetures ne sont pas une fonction.
3. Fermetures en tant que paramètres
Les fermetures sont puissantes et l'exemple suivant illustre leur utilité. Dans l'exemple, nous créons un ensemble d'états. Nous appelons la fonction de map
sur le tableau pour créer un nouveau tableau qui ne contient que les deux premières lettres de chaque état en tant que chaîne en majuscule.
1 |
var states = ["California", "New York", "Texas", "Alaska"] |
2 |
|
3 |
let abbreviatedStates = states.map({ (state: String) -> String in |
4 |
return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString |
5 |
})
|
6 |
|
7 |
println(abbreviatedStates) |
La fonction ou la méthode de map
est fréquente dans plusieurs langages de programmation et bibliothèques, comme Ruby, PHP et jQuery. Dans l'exemple ci-dessus, la fonction de map
est invoquée sur le tableau des states
, transforme son contenu et renvoie un nouveau tableau contenant les valeurs transformées.
Inférence de type
Auparavant dans cette série, nous avons appris que Swift est assez intelligent. Voyons exactement à quel point il est intelligent. Le tableau des états est un ensemble de chaînes. Parce que nous invoquons la fonction de map
sur le tableau, Swift sait que l'argument de state
est du type String
. Cela signifie que nous pouvons omettre le type tel qu'indiqué dans l'exemple mis à jour ci-dessous.
1 |
let abbreviations = states.map({ (state) -> String in |
2 |
return state.substringToIndex(advance(state.startIndex, 2)).uppercaseString |
3 |
})
|
Il y a quelques autres choses que l'on peut omettre de l'exemple ci-dessus, ce qui se traduit par un one-liner suivant.
1 |
let abbreviations = states.map({ state in state.substringToIndex(advance(state.startIndex, 2)).uppercaseString }) |
Permettez-moi d'expliquer ce qui se passe. Le compilateur peut déduire que nous renvoyons une chaîne de la fermeture que nous passons à la fonction de map
, ce qui signifie qu'il n'y a aucune raison de l'inclure dans la définition d'expression de fermeture. Nous ne pouvons que le faire si le corps de la fermeture comprend une seule déclaration. Dans ce cas, nous pouvons mettre cette déclaration sur la même ligne que la définition de la fermeture, comme le montre l'exemple ci-dessus. Étant donné qu'il n'y a pas de type de retour dans la définition et non ->
symbole précédant le type de retour, nous pouvons omettre les parenthèses entourant les paramètres de la fermeture.
Noms d'argument de la sténographie
Cela ne s'arrête pas ici. Nous pouvons utiliser les noms d'arguments abrégés pour simplifier encore plus l'expression de fermeture ci-dessus. Lorsque vous utilisez une expression de fermeture en ligne, comme dans l'exemple ci-dessus, nous pouvons omettre la liste des paramètres, y compris le mot-clé in
qui sépare les paramètres du corps de fermeture.
Dans le corps de fermeture, nous faisons référence aux arguments en utilisant les noms d'arguments abrégés que Swift nous fournit. Le premier argument est référencé par $0
, le second par $1
, etc.
Dans l'exemple mis à jour ci-dessous, j'ai omis la liste des paramètres et le mot-clé in
, et j'ai remplacé l'argument de state
dans le corps de la fermeture par le nom de l'argument abrégé $0
. L'énoncé résultant est plus concis sans compromettre la lisibilité.
1 |
let abbreviations = states.map({ $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString }) |
Fermetures de fuite
Le langage de programmation Swift définit également un concept connu sous le nom de fermetures de fin de course. L'idée est simple. Si vous passez une fermeture comme dernier argument d'une fonction, vous pouvez placer cette fermeture à l'extérieur des parenthèses de l'appel de fonction. L'exemple suivant montre comment cela fonctionne.
1 |
let abbreviations = states.map() { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString } |
Si le seul argument de l'appel de fonction est la fermeture, il est même possible d'omettre les parenthèses de l'appel de fonction.
1 |
let abbreviations = states.map { $0.substringToIndex(advance($0.startIndex, 2)).uppercaseString } |
Notez que cela fonctionne également pour les fermetures contenant plusieurs instructions. En fait, c'est la raison principale pour laquelle des fermetures de fuite sont disponibles dans Swift. Si une fermeture est longue ou complexe, et c'est le dernier argument d'une fonction, il est souvent préférable d'utiliser la syntaxe de fermeture de fuite.
1 |
let abbreviations = states.map { (state: String) -> String in |
2 |
let newState = state.substringToIndex(advance(state.startIndex, 2)) |
3 |
return newState.uppercaseString |
4 |
}
|
4. Capturer des valeurs
Lorsque vous utilisez des fermetures, vous vous trouverez souvent en utilisant ou en manipulant des constantes et des variables du contexte environnant de la fermeture dans le corps de la fermeture. Ceci est possible et souvent appelé capture de valeur. Cela signifie simplement qu'une fermeture peut capturer les valeurs de constantes et de variables à partir du contexte dans lequel elles sont définies. Prenez l'exemple suivant pour mieux comprendre le concept de capture de valeur.
1 |
func changeCase(uppercase: Bool, strings: String...) -> [String] { |
2 |
var newStrings = [String]() |
3 |
|
4 |
func changeToUppercase() { |
5 |
for s in strings { |
6 |
newStrings.append(s.uppercaseString) |
7 |
}
|
8 |
}
|
9 |
|
10 |
func changeToLowerCase() { |
11 |
for s in strings { |
12 |
newStrings.append(s.lowercaseString) |
13 |
}
|
14 |
}
|
15 |
|
16 |
if uppercase { |
17 |
changeToUppercase() |
18 |
} else { |
19 |
changeToLowerCase() |
20 |
}
|
21 |
|
22 |
return newStrings |
23 |
}
|
24 |
|
25 |
let uppercaseStates = changeCase(true, "Califorina", "New York") |
26 |
let lowercaseStates = changeCase(false, "Califorina", "New York") |
Je suis sûr que vous convenez que l'exemple ci-dessus est un peu artificiel, mais il montre clairement comment la capture de valeur fonctionne dans Swift. Les fonctions imbriquées, changeToUppercase
et changeToLowercase
, ont accès aux arguments de la fonction externe, aux states
, ainsi qu'à la variable NewStates
déclarée dans la fonction externe. Permettez-moi d'expliquer ce qui se passe.
La fonction changeCase
accepte un booléen comme premier argument et un paramètre variadique de type String
comme second paramètre. La fonction renvoie un tableau de chaînes composé des chaînes passées à la fonction en tant que deuxième argument. Dans le corps de la fonction, nous créons un ensemble mutable de chaînes, newStrings
, dans lequel nous stockons les chaînes manipulées.
Les fonctions imbriquées sur les chaînes qui passent à la fonction changeCase
et modifient le cas de chaque chaîne. Comme vous pouvez le voir, ils ont un accès direct aux chaînes transmises à la fonction changeCase
ainsi qu'au newStrings
de réseau, qui est déclaré dans le corps de la fonction changeCase
.
Nous vérifions la valeur des uppercase
, appelons la fonction appropriée et renvoyons le newStrings
de réseau. Les deux lignes à la fin de l'exemple démontrent comment fonctionne la fonction changeCase
.
Même si j'ai démontré la valeur de capture avec les fonctions, n'oubliez pas que chaque fonction est une fermeture. En d'autres termes, les mêmes règles s'appliquent aux fermetures non identifiées.
Fermetures
Il a été mentionné plusieurs fois dans cet article, les fonctions sont des fermetures. Il existe trois sortes de fermetures:
- fonctions globales
- fonctions imbriquées
- expressions de fermeture
Les fonctions globales, telles que la fonction println
de la bibliothèque standard de Swift, ne permettent pas de saisir de valeurs. Les fonctions imbriquées, cependant, ont accès et peuvent capturer les valeurs des constantes et des valeurs de la fonction dans laquelle elles sont définies. L'exemple précédent illustre ce concept.
Les expressions de fermeture, également appelées fermetures non identifiées, peuvent capturer les valeurs des constantes et des variables du contexte dans lequel elles sont définies. Ceci est très similaire aux fonctions imbriquées.
Copie et référencement
Une fermeture qui capture la valeur d'une variable est capable de modifier la valeur de cette variable. Swift est assez intelligent pour savoir s'il faut copier ou faire référence aux valeurs des constantes et des variables qu'il capture.
Les développeurs qui sont nouveaux chez Swift et ont peu d'expérience avec d'autres langages de programmation prendront ce comportement pour acquis. Cependant, c'est un avantage important que Swift comprend comment les valeurs capturées sont utilisées dans une fermeture et, par conséquent, peuvent gérer la gestion de la mémoire pour nous.
En savoir plus dans notre cours de programmation Swift
Si vous désirez passer votre éducation Swift au niveau suivant, vous pouvez consulter notre cours complet sur le développement de Swift.
Conclusion
Les fermetures sont un concept important et vous les utiliserez souvent dans Swift. Ils vous permettent d'écrire un code flexible et dynamique à la fois facile à écrire et à comprendre. Dans l'article suivant, nous explorerons la programmation orientée objet dans Swift, en commençant par des objets, des structures et des classes.