Swift From Scratch Fermetures

Si vous avez travaillé avec des blocs en C ou Objective-C ou des lambdas en Ruby, vous n'aurez pas de difficulté à comprendre le concept de fermeture. Les fermetures ne sont rien de plus que des blocs de fonctionnalités que vous pouvez transmettre dans votre code.

En fait, nous avons déjà travaillé avec les fermetures dans les leçons précédentes. C'est vrai: les fonctions sont aussi des fermetures. Commençons par les bases et examinons 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 en tant qu'argument d'une fonction ou vous pouvez la stocker en tant que propriété d'un objet. Les fermetures ont de nombreux cas d'utilisation.

Le nom fermeture fait allusion à l’une des caractéristiques clés des fermetures. Une fermeture capture les variables et les constantes du contexte dans lequel elle est définie. Ceci est parfois appelé fermeture sur ces variables et constantes. Nous allons examiner la saisie de valeur plus en détail à la fin de cette leçon..

La flexibilité

Vous avez déjà appris que les fonctions peuvent être incroyablement puissantes et flexibles. Comme les fonctions sont des fermetures, les fermetures sont tout aussi flexibles. Dans cet article, vous découvrez à 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 à Swift ont cependant quelques avantages. L'un des principaux avantages des fermetures dans Swift est que la gestion de la mémoire est une chose pour laquelle vous, le développeur, n'avez pas à vous soucier..

Même les cycles de rétention, qui ne sont pas rares en C ou en Objective-C, sont gérés par Swift. Cela réduit les fuites de mémoire difficiles à trouver ou les crashs causés par des pointeurs invalides.

2. Syntaxe

La syntaxe de base d'une fermeture n'est pas difficile et peut vous rappeler des fonctions globales et imbriquées, que nous avons abordées précédemment dans cette série. Regardez l'exemple suivant.

(a: Int) -> Int en retour a + 1

La première chose que vous remarquerez, c’est que toute la fermeture est entourée d’une paire d’accolades. Les paramètres de la fermeture sont encadrés par une paire de parenthèses, séparées du type de retour par le -> symbole. La fermeture ci-dessus accepte un argument, une, de type Int, et retourne un Int. Le corps de la fermeture commence après la dans mot-clé.

Les fermetures nommées, c'est-à-dire les fonctions globales et imbriquées, sont un peu différentes. L’exemple suivant devrait illustrer les différences.

func increment (_ a: Int) -> Int return a + 1

Les différences les plus importantes sont l’utilisation de la func mot-clé et la position des paramètres et le type de retour. Une fermeture commence et se termine par une accolade enveloppant les paramètres, le type de retour et le corps de la fermeture. Malgré ces différences, rappelez-vous que chaque fonction est une fermeture. Chaque fermeture n’est pas une fonction, bien que.

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 tableau d'états. Nous invoquons le carte(_:) fonction sur le tableau pour créer un nouveau tableau contenant uniquement les deux premières lettres de chaque état sous forme de chaîne en majuscule.

var states = ["Californie", "New York", "Texas", "Alaska"] let abbreviatedStates = states.map ((state: String) -> Chaîne dans let index = state.index (state.startIndex, offsetBy : 2) return state.substring (to: index) .uppercased ()) print (états abrégés)

le carte(_:) la fonction ou la méthode est commune à de nombreux langages de programmation et bibliothèques, tels que Ruby, PHP et JavaScript. Dans l'exemple ci-dessus, le carte(_:) la fonction est appelée sur le États array, transforme son contenu et renvoie un nouveau tableau contenant les valeurs transformées. Ne vous inquiétez pas du corps de la fermeture pour l'instant.

Inférence de type

Précédemment dans cette série, nous avons appris que Swift est assez intelligent. Laissez-moi vous montrer exactement à quel point intelligent. Le tableau d'états est un tableau de chaînes. Parce que nous invoquons le carte(_:) fonction sur le tableau, Swift sait que le Etat l'argument est de type Chaîne. Cela signifie que nous pouvons omettre le type, comme indiqué dans l'exemple mis à jour ci-dessous.

let abbreviatedStates = states.map ((state) -> Chaîne dans let index = state.index (state.startIndex, offsetBy: 2) renvoie state.substring (to: index) .uppercased ())

Il y a quelques autres choses que nous pouvons omettre de l'exemple ci-dessus, ce qui donne la ligne suivante.

let abbreviatedStates = states.map (state in state.substring (à: state.index (state.startIndex, offsetBy: 2)). uppercased ())

Laisse moi expliquer ce qui se passe. 

Le compilateur peut en déduire que nous renvoyons une chaîne de la fermeture que nous passons à la carte(_:) fonction, ce qui signifie qu'il n'y a aucune raison de l'inclure dans la définition de l'expression de fermeture. 

Nous ne pouvons le faire que si le corps de la fermeture contient une seule déclaration. Dans ce cas, nous pouvons placer cette déclaration sur la même ligne que la définition de la fermeture, comme indiqué dans l'exemple ci-dessus. Parce qu'il n'y a pas de type de retour dans la définition et pas -> symbole précédant le type de retour, nous pouvons omettre les parenthèses entourant les paramètres de la fermeture.

Noms d'arguments abrégés

Cela ne s'arrête pas là, cependant. Nous pouvons utiliser des 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, vous pouvez omettre la liste des paramètres, y compris dans mot clé qui sépare les paramètres du corps de la fermeture.

Dans le corps de la fermeture, nous référençons les arguments à l'aide de noms d'arguments abrégés fournis par Swift. Le premier argument est référencé par 0 $, la seconde par 1 $, etc.

Dans l'exemple mis à jour ci-dessous, j'ai omis la liste des paramètres et les dans mot-clé et a remplacé le Etat argument dans le corps de la fermeture avec le nom d'argument abrégé 0 $. La déclaration résultante est plus concise sans compromettre la lisibilité.

let abbreviatedStates = states.map ($ 0.substring (to: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ())

Fermetures de fuite

Le langage de programmation Swift définit également un concept appelé fermeture de fin. L'idée est simple. Si vous passez une fermeture comme dernier argument d'une fonction, vous pouvez placer cette fermeture en dehors des parenthèses de l'appel de la fonction. L'exemple suivant montre comment cela fonctionne.

let abbreviatedStates = states.map () $ 0.substring (to: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ()

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.

let abbreviatedStates = states.map $ 0.substring (to: $ 0.index ($ 0.startIndex, offsetBy: 2)). uppercased ()

Notez que cela fonctionne également pour les fermetures contenant plusieurs instructions. En fait, c’est la raison principale pour laquelle les fermetures de fuite sont disponibles dans Swift. Si une fermeture est longue ou complexe et qu'il s'agit du dernier argument d'une fonction, il est souvent préférable d'utiliser la syntaxe de fermeture finale..

let abbreviatedStates = states.map (state) -> Chaîne dans let index = state.index (state.startIndex, offsetBy: 2) renvoie state.substring (to: index) .uppercased ()

4. Capturer les valeurs

Lorsque vous utilisez des fermetures, vous vous retrouvez souvent à utiliser ou à manipuler des constantes et des variables du contexte environnant de la fermeture dans le corps de la fermeture. Ceci est souvent appelé capture de valeur. Cela signifie simplement qu'une fermeture peut capturer les valeurs de constantes et de variables du contexte dans lequel elle est définie. Prenons l'exemple suivant pour mieux comprendre le concept de capture de valeur.

func changeCase (majuscule: Bool, chaînes ofStrings: String…) -> [Chaîne] var newStrings = [Chaîne] () func changeToUppercase () pour s dans les chaînes newStrings.append (s.uppercased ()) func changeToLowerCase () pour s dans les chaînes newStrings.append (s.lowercased ()) si la majuscule changeToUppercase () else changeToLowerCase () retour newStrings let uppercasedStates = changeCase (uppercase: true, ofStrings: " "," New York ") laissez lowercasedStates = changeCase (majuscule: false, ofStrings:" Californie "," New York ")

Je suis sûr que vous êtes d'accord pour dire 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 (), avoir accès aux arguments de la fonction externe, États, aussi bien que nouveaux états variable déclarée dans la fonction externe. 

Laisse moi expliquer ce qui se passe.

le changeCase (majuscule: ofStrings :) la fonction accepte un booléen comme premier argument et un paramètre variadique de type Chaîne comme second paramètre. La fonction renvoie un tableau de chaînes composé des chaînes transmises à la fonction en tant que deuxième argument. Dans le corps de la fonction, nous créons un tableau mutable de chaînes, nouveauStrings, dans lequel nous stockons les chaînes modifiées.

Les fonctions imbriquées parcourent les chaînes transmises au changeCase (majuscule: ofStrings :) fonction et changer la casse de chaque chaîne. Comme vous pouvez le constater, ils ont un accès direct aux chaînes passées au changeCase (majuscule: ofStrings :) fonction ainsi que le nouveauStrings tableau, qui est déclaré dans le corps de la changeCase (majuscule: ofStrings :) une fonction.

Nous vérifions la valeur de majuscule, appeler la fonction appropriée et renvoyer le nouveauStrings tableau. Les deux lignes à la fin de l'exemple montrent comment le changeCase (majuscule: ofStrings :) fonction fonctionne.

Même si j'ai démontré la capture de valeur avec des fonctions, souvenez-vous que chaque fonction est une fermeture. En d'autres termes, les mêmes règles s'appliquent aux fermetures sans nom..

Fermetures

Cela a été mentionné à plusieurs reprises dans cet article: les fonctions sont des fermetures. Il existe trois types de fermetures:

  • fonctions globales
  • fonctions imbriquées
  • expressions de fermeture

Des fonctions globales, telles que print (_: separator: terminator :) fonction de la bibliothèque standard Swift, ne capturez aucune valeur. Les fonctions imbriquées, cependant, ont accès aux valeurs des constantes et à celles de la fonction dans laquelle elles sont définies et peuvent les capturer. L'exemple précédent illustre ce concept..

Les expressions de fermeture, également appelées fermetures sans nom, peuvent capturer les valeurs des constantes et des variables du contexte dans lequel elles sont définies. Cela 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 changer la valeur de cette variable. Swift est assez intelligent pour savoir s’il faut copier ou référencer les valeurs des constantes et des variables qu’il capture..

Les développeurs qui apprennent Swift et qui ont peu d’expérience avec d’autres langages de programmation prendront ce comportement pour acquis. Cependant, Swift comprend bien comment les valeurs capturées sont utilisées dans une fermeture et, par conséquent, peut gérer la gestion de la mémoire pour nous..

Conclusion

Les fermetures sont un concept important et vous les utiliserez souvent dans Swift. Ils vous permettent d’écrire du code flexible et dynamique, facile à écrire et à comprendre.. 

Dans le prochain article, nous explorons la programmation orientée objet dans Swift, en commençant par les objets, les structures et les classes..

Si vous souhaitez apprendre à utiliser Swift 3 pour coder des fonctionnalités avancées d'applications réelles, consultez notre cours intitulé Aller plus loin avec Swift: animation, mise en réseau et contrôles personnalisés. Suivez Markus Mühlberger pendant qu'il code une application météo iOS fonctionnelle avec des données météo en direct, des composants d'interface utilisateur personnalisés et des animations fluides pour donner vie à tout..