qui prennent en charge les fonctions de première classe vous permettent d’utiliser des fonctions et des méthodes comme n’importe quel autre objet ou valeur. Vous pouvez les passer en arguments, les enregistrer dans des propriétés ou les renvoyer depuis une autre fonction. Dans l’ordre des mots, la langue traite les fonctions comme des « citoyens de première classe ».

Bien que Swift ne soit pas le premier langage à prendre en charge cette façon de gérer les fonctions, c’est normalement une fonctionnalité que vous voyez dans des langages plus dynamiques comme JavaScript ou Lua. Donc, combiner le système de type statique robuste de Swift avec des fonctions de première classe devient une combinaison assez intéressante, et peut nous permettre de faire des choses assez créatives 😀.

Cette semaine, jetons un coup d’œil à quelques façons différentes d’utiliser des fonctions de première classe dans Swift!

Instabug: Résolvez les bogues, les plantages et autres problèmes beaucoup plus rapidement en utilisant les traces de pile détaillées, les journaux réseau et les événements d’interface utilisateur qu’Instabug attache automatiquement à chaque rapport de bogue. Utilisé à la fois par moi et par des milliers d’équipes de développement iOS à travers le monde. Essayez-le gratuitement et intégrez-le avec une seule ligne de code.

Passer des fonctions en arguments

Commençons par les bases. Puisque les fonctions peuvent être utilisées comme valeurs, cela signifie que nous pouvons les passer comme arguments. Par exemple, disons que nous voulons ajouter un tableau de sous-vues à une vue. Normalement, nous pourrions faire quelque chose comme ça:

let subviews = subviews.forEach { subview in view.addSubview(subview)}

Le code ci-dessus fonctionne, et il n’y a rien de mal à cela. Mais si nous profitons des fonctions de première classe, nous pouvons en fait réduire beaucoup sa verbosité.

Ce que nous pouvons faire, c’est traiter la méthode addSubview comme une fermeture de type (UIView) -> Void (car elle accepte une vue à ajouter et ne renvoie rien). Cela correspond parfaitement au type d’argument que forEach accepte (une fermeture de type (Element) -> Void, et dans ce cas le type Element est UIView). Le résultat est que nous pouvons passer view.addSubview directement en argument à notre appel forEach, comme ceci:

subviews.forEach(view.addSubview)

C’est plutôt cool ! However Cependant, une chose à garder à l’esprit, c’est que lorsque vous utilisez des méthodes d’instance en tant que fermetures comme celle-ci, vous conservez automatiquement l’instance tant que vous conservez la fermeture. Ce n’est pas du tout un problème lors du passage d’une fonction comme argument de fermeture sans échappement comme ci-dessus, mais pour échapper aux fermetures, il faut en être conscient afin d’éviter les cycles de rétention.

Pour plus d’informations sur les arguments de fermeture échappante et non échappante et la capture, consultez « Capture d’objets dans des fermetures rapides ».

Passer des initialiseurs en tant qu’arguments

Ce qui est cool, c’est que ce ne sont pas seulement les fonctions et les méthodes qui peuvent être utilisées comme fonctions de première classe dans Swift – vous pouvez également utiliser des initialiseurs de cette façon.

Par exemple, disons que nous avons un tableau d’images pour lequel nous aimerions créer des vues d’image et que nous voulons ajouter chacune de ces vues d’image à une vue de pile. En utilisant des fonctions de première classe, nous pouvons réaliser tout ce qui précède en utilisant une chaîne simple de map et forEach:

images.map(UIImageView.init) .forEach(stackView.addArrangedSubview)

Ce que j’aime dans la structuration du code de cette façon, c’est qu’il devient très déclaratif. Au lieu de boucles for imbriquées, nous déclarons simplement ce que nous voulons que le résultat soit. Il y a bien sûr un équilibre à trouver entre le code déclaratif, compact et la lisibilité, mais pour des opérations simples comme celles ci-dessus, je pense que tirer parti des fonctions de première classe peut être super agréable.

Vous pouvez trouver plus d’exemples et de cas d’utilisation pour passer des initialiseurs en tant que fermetures dans « Injection de dépendance Swift simple avec fonctions » et « Voyager dans le temps dans les tests unitaires Swift ».

Création de références de méthode d’instance

Plongeons un peu plus profondément dans le monde merveilleux des fonctions de première classe 😉. Une chose qui m’a intrigué le plus longtemps était le fait que j’ai reçu des suggestions de complétion automatique de la méthode d’instance lorsque je voulais appeler une méthode statique. Essayez de taper UIView. dans Xcode pour voir ce que je veux dire, vous obtenez chaque méthode d’instance comme suggestion 🤔.

Au début, je pensais qu’il s’agissait d’un bogue Xcode, mais j’ai ensuite décidé d’enquêter. Il s’avère que pour chaque méthode d’instance d’un type, il existe une méthode statique correspondante qui vous permet de récupérer cette méthode d’instance en tant que fermeture, en passant une instance en argument.

Par exemple, nous pouvons utiliser ce qui suit pour récupérer une référence à la méthode removeFromSuperview pour une instance UIView donnée:

let closure = UIView.removeFromSuperview(view)

Appeler la fermeture ci-dessus serait exactement la même chose que d’appeler view.removeFromSuperview(), ce qui est intéressant, mais est-ce vraiment utile? Jetons un coup d’œil à quelques scénarios où l’utilisation de cette fonctionnalité peut en fait conduire à des résultats assez sympas.

XCTest sur Linux

Une façon dont l’un des frameworks d’Apple utilise cette fonctionnalité est d’exécuter des tests à l’aide de XCTest sur Linux. Sur les propres plates-formes d’Apple, XCTest fonctionne en utilisant le runtime Objective-C pour rechercher toutes les méthodes de test pour un cas de test donné, puis les exécute automatiquement. Cependant, sous Linux, il n’y a pas d’exécution Objective-C, donc cela nous oblige à écrire un peu de passe-partout pour exécuter nos tests.

Tout d’abord, nous devons déclarer un dictionnaire statique allTests qui contient un mappage entre nos noms de test et les méthodes réelles à exécuter:

Nous passons ensuite le dictionnaire ci-dessus à la fonction XCTMain pour exécuter nos tests:

XCTMain()

Sous le capot, cela utilise la fonctionnalité de pouvoir extraire des méthodes d’instance en utilisant son équivalent statique, ce qui nous permet de simplement faire référence aux fonctions par leur nom dans un contexte statique, tout en permettant au framework de générer des méthodes d’instance à exécuter. Plutôt malin ! 👍

Sans cette fonctionnalité, nous aurions dû écrire quelque chose comme ceci:

Appelant une méthode d’instance sur chaque élément d’une séquence

Prenons cette fonctionnalité pour un tour nous-mêmes. Tout comme nous avons pu passer la méthode d’instance d’un autre objet en argument à forEach, ne serait-il pas cool si nous pouvions également passer une méthode d’instance que nous voulons que chaque élément d’une séquence exécute?

Par exemple, disons que nous avons un tableau de sous-vues que nous voulons supprimer de leur vue d’ensemble. Au lieu d’avoir à le faire:

for view in views { view.removeFromSuperview()}

Ce ne serait pas cool si nous pouvions le faire à la place:

views.forEach(UIView.removeFromSuperview)

La bonne nouvelle est que nous pouvons, tout ce que nous avons à faire est de créer une petite extension sur Sequence qui accepte l’une de ces méthodes d’instance référencées statiquement. Comme ce sont des fonctions qui génèrent une fonction (Functionception! their) leur type sera toujours (Type) -> (Input) -> Output, donc pour notre extension, nous pouvons créer une surcharge forEach qui accepte une fermeture de ce type:

Nous pouvons maintenant facilement appeler des méthodes d’instance sur chaque membre de n’importe quelle séquence! Implementing

Implémentation de target / action sans Objective-C

Jetons un coup d’œil à un autre exemple. Dans UIKit, le modèle cible / action est très courant, de l’observation des clics sur les boutons à la réponse aux gestes. Personnellement, j’aime vraiment ce modèle, car il nous permet d’utiliser facilement une méthode d’instance comme rappel sans avoir à nous soucier du problème de cycle de rétention que nous avons discuté plus tôt (lors du référencement d’une méthode d’instance en tant que fermeture).

Cependant, la façon dont target/action est implémentée dans UIKit repose sur des sélecteurs Objective-C (c’est pourquoi vous devez annoter les méthodes d’action privées avec @objc). Disons que nous voulions ajouter le modèle cible / action à l’une de nos vues personnalisées, et disons que nous voulons le faire sans compter sur les sélecteurs Objective-C. Cela peut sembler beaucoup de travail et cela rendra les choses terriblement compliquées, mais grâce aux fonctions de première classe, c’est assez simple! 😀

Commençons par définir un typealias Action comme une fonction statique qui renvoie une méthode d’instance pour un type et une entrée donnés:

typealias Action<Type, Input> = (Type) -> (Input) -> Void

Ensuite, créons notre point de vue. Nous allons créer un ColorPicker qui permet à l’utilisateur de choisir une couleur dans une application de dessin et d’y ajouter une méthode pour y ajouter une action & cible. Nous garderons une trace de toutes les observations en tant que fermetures, et chaque fois qu’une fermeture est exécutée, nous générons une méthode d’instance pour la cible donnée et l’exécutons, comme ceci:

La chose cool est que nous pouvons réellement utiliser des fonctions de première classe encore plus haut. En utilisant l’API map sur Optional, nous pouvons générer la méthode d’instance et l’appeler en une seule fois, comme ceci:

observations.append { view in target.map(action)?(view)}

Enfin, utilisons notre nouvelle API target /action dans un CanvasViewController, qui présentera notre ColorPicker. Tout comme nous ajouterions une action target & à une action UIButton ou UIGestureRecognizer, nous pouvons simplement passer le contrôleur de vue lui-même et une méthode d’instance à exécuter, comme ceci :

Tapez des actions safe targets & sans aucun sélecteur Objective-C ni risque de fuites de mémoire, en utilisant seulement quelques lignes de code – assez cool! Support

Soutenez Swift by Sundell en consultant ce sponsor:

Instabug: Résolvez les bogues, les plantages et autres problèmes beaucoup plus rapidement en utilisant les traces de pile détaillées, les journaux réseau et les événements d’interface utilisateur qu’Instabug attache automatiquement à chaque rapport de bogue. Utilisé à la fois par moi et par des milliers d’équipes de développement iOS à travers le monde. Essayez-le gratuitement et intégrez-le avec une seule ligne de code.

Conclusion

Les fonctions de première classe sont une fonctionnalité très puissante. En étant capable d’utiliser des fonctions et des méthodes de manière beaucoup plus dynamique, nous pouvons obtenir des résultats assez intéressants, et cela peut être très utile lors de l’implémentation de certains types d’API.

Cependant, selon les mots célèbres de l’oncle Ben; avec un grand pouvoir vient une grande responsabilité. Bien que je pense qu’il est super utile d’en apprendre davantage sur ce genre de fonctionnalités et leur fonctionnement, il est également important de faire preuve de retenue lors de leur utilisation. Notre objectif devrait toujours être de créer des API agréables et faciles à utiliser, et d’écrire du code facile à lire & maintenir. Les fonctions de première classe peuvent certainement nous aider à atteindre cet objectif, mais si elles sont prises trop loin, elles peuvent également conduire à tout le contraire. Comme toujours, ma recommandation est d’expérimenter, d’essayer ces fonctionnalités et de voir par vous-même si et comment elles peuvent être utilisées dans votre propre code.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.