IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Apple publie la version 5.5 de Swift avec le mot-clé async pour corriger la "pyramide condamnée",
Ainsi que d'autres changements dans une "version massive"

Le , par Bill Fassinou

13PARTAGES

6  0 
Apple a publié ce lundi Swift 5.5, la dernière version de son langage de programmation open source pour la création d'applications sur iOS, macOS, watchOS et tvOS. Décrite comme une "version massive", la version 5.5 de Swift comprend les mots-clés async/await, des collections de paquets et une interopérabilité Objective-C améliorée. Swift 5.5 comporte de nouvelles fonctionnalités particulièrement intéressantes pour les développeurs qui cherchent à utiliser la concurrence pour accélérer leurs applications.

Swift 5.5 introduit les fonctions async/await

Les fonctions asynchrones constituent l'un des principaux ajouts de Swift 5.5. L'un des problèmes de l'approche antérieure de Swift en matière de concurrence était que lorsqu'un développeur écrivait plusieurs opérations asynchrones, il en résultait une "pyramide condamnée" (pyramid of doom) - des appels imbriqués difficiles à suivre, qui rendaient le contrôle du flux et la gestion des erreurs problématiques "parce que le mécanisme naturel de gestion des erreurs de Swift ne peut pas être utilisé. La solution a été d'adopter le modèle async/await tel qu'il est vu dans le C# de Microsoft (inspiré par les flux de travail asynchrones de F#).

Marquées par le mot-clé async et appelées par await, les fonctions asynchrones fonctionnent de manière similaire aux fonctions asynchrones de Python et de JavaScript. Swift prend désormais en charge du code comme celui-ci :

Code Swift : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
  
func loadWebResource(_ path: String) async throws -> Resource 
func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image 
func dewarpAndCleanupImage(_ i : Image) async throws -> Image 
  
func processImageData() async throws -> Image { 
  let dataResource  = try await loadWebResource("dataprofile.txt") 
  let imageResource = try await loadWebResource("imagedata.dat") 
  let imageTmp      = try await decodeImage(dataResource, imageResource) 
  let imageResult   = try await dewarpAndCleanupImage(imageTmp) 
  return imageResult 
}

Avant leur introduction, les programmeurs devaient utiliser des fermetures de complétion pour s'assurer que les fonctions ne se bloquaient pas mutuellement, ce qui rendait le code quelque peu difficile à lire et sujet aux erreurs. Les fonctions asynchrones sont censées remédier à ce problème, ainsi qu'une longue liste de fonctions de concurrence qui ont également été intégrées dans la dernière version du langage. Par exemple, Swift 5.5 ajoute un protocole AsyncSequence, qui vise à rendre l'itération sur des séquences de valeurs asynchrones aussi facile que le bouclage de son équivalent synchrone.

L'utilisation est donc très proche de celle d'une séquence ordinaire, bien que la méthode next() soit ici asynchrone. Pour faciliter la combinaison de code synchrone et de tâches asynchrones, il existe également ce que l'on appelle des continuations qui permettent aux tâches de se suspendre, mais qui transmettent également une valeur que le code synchrone peut utiliser pour relancer la tâche.

https://youtu.be/6C0SFPEy_0Y

Comme l'asynchronisme ne suffit pas à faire un programme concurrent, l'équipe a mis en œuvre une proposition de concurrence structurée, qui stipule que "toutes les fonctions asynchrones s'exécutent dans le cadre d'une tâche asynchrone". Toutes les tâches peuvent créer des tâches enfant qui s'exécutent simultanément, de sorte qu'il y a une hiérarchie dans le système et, avec elle, une façon de partager l'information qui est censée rendre la construction "pratique à gérer".

L'interopérabilité avec Objective-C et les "acteurs"

Selon l'équipe, les fermetures anonymes sont implicitement asynchrones si elles contiennent une expression [C=Swift]await[/B]. Ce nouveau modèle de concurrence améliorera également l'interopérabilité avec Objective-C, l'ancien langage d'Apple. Objective-C dispose de méthodes de gestion de l'achèvement (completion-handler methods) qui seront traduites en méthodes asynchrones dans Swift, et les méthodes asynchrones de Swift peuvent également être exportées en tant que méthodes de gestion de l'achèvement. En outre, un deuxième élément clé du modèle de concurrence de Swift est les acteurs.

Décrits comme un type de référence qui protège l'accès à son état mutable, les acteurs sont introduits avec le mot-clé actor. Selon l'équipe, les acteurs sont similaires aux classes, mais s'assurent que les états mutables ne sont accessibles que par un seul thread à la fois. « Parce qu'ils sont incarnés par une abstraction de file d'attente (interne), vous communiquez avec les acteurs de manière asynchrone, et ils garantissent que les données qu'ils protègent ne sont touchées que par le code s'exécutant sur cette file d'attente. On obtient ainsi un îlot de sérialisation dans une mer de concurrence », a déclaré l'inventeur de Swift, Chris Lattner.

Pour illustrer le problème que les acteurs résolvent, considérons ce code Swift qui crée une classe RiskyCollector capable d'échanger des cartes de son jeu avec un autre collecteur :

Code Swift : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  
class RiskyCollector { 
    var deck: Set<String> 
  
    init(deck: Set<String>) { 
        self.deck = deck 
    } 
  
    func send(card selected: String, to person: RiskyCollector) -> Bool { 
        guard deck.contains(selected) else { return false } 
  
        deck.remove(selected) 
        person.transfer(card: selected) 
        return true 
    } 
  
    func transfer(card: String) { 
        deck.insert(card) 
    } 
}

Dans un environnement à monothread, ce code est sûr : nous vérifions si notre jeu contient la carte en question, nous la retirons, puis nous l'ajoutons au jeu de l'autre collecteur. Cependant, dans un environnement multithread, le code présente une condition de course potentielle, c'est-à-dire un problème où les résultats du code varient lorsque deux parties distinctes du code sont exécutées côte à côte. Si l'on appelle send(card:to :) plus d'une fois en même temps, la chaîne d'événements suivante peut se produire :

  • le premier thread vérifie si la carte est dans le paquet, et si c'est le cas, il continue ;
  • le deuxième thread vérifie également si la carte est dans le paquet, et elle l'est, donc il continue ;
  • le premier thread retire la carte du jeu et la transfère à l'autre personne ;
  • le deuxième thread tente de retirer la carte du jeu, mais en fait, elle est déjà partie et rien ne se passe. Cependant, il transfère quand même la carte à l'autre personne.


Dans cette situation, un joueur perd une carte tandis que l'autre en gagne deux, et si cette carte est, par exemple, un Lotus noir de Magic the Gathering (un jeu de cartes à jouer et à collectionner inventé par Richard Garfield en 1993), vous avez un gros problème. Les acteurs résolvent ce problème en introduisant l'isolation de l'acteur : les propriétés et les méthodes stockées ne peuvent pas être lues depuis l'extérieur de l'objet acteur, sauf si elles sont exécutées de manière asynchrone, et les propriétés stockées ne peuvent pas du tout être écrites depuis l'extérieur de l'objet acteur.

Le comportement asynchrone n'est pas là pour les performances, mais parce que Swift place automatiquement ces demandes dans une file d'attente qui est traitée séquentiellement pour éviter les situations de course. Alors, il est possible de réécrire la classe RiskyCollector pour en faire un acteur SafeCollector, comme ceci :

Code Swift : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  
actor SafeCollector { 
    var deck: Set<String> 
  
    init(deck: Set<String>) { 
        self.deck = deck 
    } 
  
    func send(card selected: String, to person: SafeCollector) async -> Bool { 
        guard deck.contains(selected) else { return false } 
  
        deck.remove(selected) 
        await person.transfer(card: selected) 
        return true 
    } 
  
    func transfer(card: String) { 
        deck.insert(card) 
    } 
}

Il y a plusieurs choses à remarquer dans cet exemple :

  • les acteurs sont créés à l'aide du nouveau mot-clé actor. Il s'agit d'un nouveau type nominal concret dans Swift, qui rejoint les structs, les classes et les enum ;
  • la méthode send() est marquée async, car elle devra suspendre son travail en attendant que le transfert soit terminé ;
  • bien que la méthode transfer(card :) ne soit pas marquée async, l'on doit quand même l'appeler avec await, car elle attendra que l'autre acteur SafeCollector soit en mesure de traiter la demande.


Pour être clair, un acteur peut utiliser ses propres propriétés et méthodes librement, de manière asynchrone ou non, mais lorsqu'il interagit avec un autre acteur, cela doit toujours être fait de manière asynchrone. Grâce à ces modifications, Swift peut s'assurer que tous les états isolés des acteurs ne sont jamais accédés simultanément et, plus important encore, que cela est fait au moment de la compilation, de sorte que la sécurité est garantie. Les acteurs et les classes présentent certaines similitudes :

  • les deux sont des types de référence, ils peuvent donc être utilisés pour un état partagé ;
  • ils peuvent avoir des méthodes, des propriétés, des initialisateurs et des souscripteurs ;
  • ils peuvent se conformer à des protocoles et être génériques ;
  • toutes les propriétés et méthodes statiques se comportent de la même manière dans les deux types, car elles n'ont pas de concept de soi et ne sont donc pas isolées.


Outre l'isolation des acteurs, il existe deux autres différences importantes entre les acteurs et les classes :

  • les acteurs ne supportent pas actuellement l'héritage, ce qui rend leurs initialisateurs beaucoup plus simples - il n'y a pas besoin d'initialisateurs de commodité, de surcharge, du mot-clé final, et plus encore. Cela pourrait changer à l'avenir ;
  • tous les acteurs se conforment implicitement à un nouveau protocole Actor ; aucun autre type concret ne peut l'utiliser. Cela vous permet de restreindre d'autres parties de votre code afin qu'il ne puisse fonctionner qu'avec des acteurs.


Les collections et d'autres fonctionnalités

Swift 5.5 introduit le concept de collections de paquets. Comme son nom l'indique, une collection de paquets est un groupe de paquets, où un paquet est un ensemble de fichiers de code source Swift et un manifeste. L'approche vise à fournir aux éducateurs et aux autres membres de la communauté un moyen de regrouper des paquets pour des cas d'utilisation spécifiques et de les partager facilement. Selon la proposition, les collections sont réalisées par des documents JSON statiques contenant des listes de paquets et des métadonnées supplémentaires, ce qui les rend également consultables.

« Nous envisageons que les éducateurs et les influenceurs de la communauté publient des collections de paquets pour accompagner les supports de cours ou les articles de blogue, en supprimant les frictions liées à l'utilisation de paquets pour la première fois et la surcharge cognitive consistant à décider quels paquets sont utiles pour une tâche particulière », indiquait la proposition initiale de cette fonctionnalité. Les collections de paquets sont prises en charge par l'index indépendant Swift Package Index et intégrées à Xcode 13, la nouvelle version qu'Apple a publiée pour s'aligner sur sa version iOS 15 et l'iPhone 13.

Si la concurrence a fait un grand pas en avant, Swift 5.5 ne se contente pas d'être asynchrone et d'attendre. Il met aussi fin aux problèmes de conversion CGFloat en étendant le langage pour permettre aux types Double et CGFloat d'être interchangeables grâce à une conversion implicite. Swift 5.5 est disponible dès maintenant dans le cadre de Xcode 13, et peut également être téléchargé pour Ubuntu, CentOS et Amazon Linux 2. Une version Windows 10 est "à venir".

Les détails de toutes les modifications apportées au langage se trouvent dans le dépôt GitHub du langage. Il sera intéressant de voir si la mise à jour permet d'impliquer davantage de personnes dans le langage, puisque l'adoption du langage ciblant les plateformes Apple semble avoir été au mieux stagnante ces derniers temps. Swift se classe 11e dans le classement des langages de Redmonk (où il se trouve depuis 2018), juste devant son rival maison, Objective-C, et 20e dans le sondage des développeurs de StackOverflow.

Source : Swift 5.5

Et vous ?

Que pensez-vous des nouveautés introduites par Swift 5.5 ?

Voir aussi

Swift 5 est disponible avec de nouveaux types de données, une stabilité de l'ABI, une réimplémentation UTF-8 de String et bien d'autres

Swift 5.2 est disponible avec une nouvelle architecture de diagnostic qui vise à améliorer la qualité et la précision des messages d'erreur

Swift 5.3 est disponible avec de meilleures performances et axée sur une meilleure productivité et améliore l'ergonomie de l'écriture du code Swift

AppCode 2021.2 est disponible : découvrez les nouveautés de l'EDI de JetBrains pour les plateformes Apple

Une erreur dans cette actualité ? Signalez-nous-la !