Articles

Littéraux de chaînes dans Swift

Pouvoir exprimer des valeurs de base, telles que des chaînes et des entiers, en utilisant des littéraux en ligne est une caractéristique essentielle dans la plupart des langages de programmation. Cependant, alors que de nombreux autres langages prennent en charge des littéraux spécifiques intégrés dans leur compilateur, Swift adopte une approche beaucoup plus dynamique — en utilisant son propre système de types pour définir la façon dont les différents littéraux doivent être traités, via des protocoles.

Cette semaine, concentrons—nous sur les littéraux de chaîne en particulier, en examinant les nombreuses façons dont ils peuvent être utilisés et comment nous – grâce à la conception hautement axée sur le protocole de Swift — sommes en mesure de personnaliser la façon dont les littéraux sont interprétés, ce qui nous permet de faire des choses vraiment intéressantes.

Développeur essentiel

Développeur essentiel: Rejoignez un cours intensif en ligne gratuit pour les développeurs iOS qui souhaitent devenir des développeurs seniors ceinture noire — c’est-à-dire: atteignez un niveau expert de compétences pratiques et faites partie des développeurs les mieux payés au monde.

Les bases

Tout comme dans de nombreux autres langages, les chaînes Swift sont exprimées par des littéraux entourés de guillemets — et peuvent contenir à la fois des séquences spéciales (telles que des sauts de ligne), des caractères échappés et des valeurs interpolées:

let string = "\(user.name) says \"Hi!\"\nWould you like to reply?"// John says "Hi!"// Would you like to reply?

Bien que les fonctionnalités utilisées ci-dessus nous offrent déjà beaucoup de flexibilité, et sont très probablement suffisantes pour la grande majorité des cas d’utilisation, il existe des situations dans lesquelles des moyens plus puissants d’exprimer les littéraux peuvent être utiles. Jetons un coup d’œil à certains d’entre eux, en commençant par le moment où nous devons définir une chaîne contenant plusieurs lignes de texte.

Littéraux multilignes

Bien que tout littéral de chaîne standard puisse être divisé en plusieurs lignes en utilisant \n, ce n’est pas toujours pratique — surtout si nous cherchons à définir un plus grand morceau de texte en tant que littéral en ligne.

Heureusement, depuis Swift 4, nous sommes également en mesure de définir des littéraux de chaînes multilignes en utilisant trois guillemets au lieu d’un seul. Par exemple, nous utilisons ici cette fonctionnalité pour afficher un texte d’aide pour un script Swift, au cas où l’utilisateur ne transmettrait aucun argument lors de son appel sur la ligne de commande:

// We're comparing against 1 here, since the first argument passed// to any command line tool is the current path of execution.guard CommandLine.arguments.count > 1 else { print(""" To use this script, pass the following: - A string to process - The maximum length of the returned string """) // Exit the program with a non-zero code to indicate failure. exit(1)}

Ci-dessus, nous utilisons le fait que les littéraux de chaînes multilignes conservent l’indentation de leur texte, par rapport à l’ensemble de guillemets de terminaison, en bas. Ils nous permettent également d’utiliser beaucoup plus librement des guillemets non échappés en leur sein, car ils sont définis par un ensemble de trois guillemets, ce qui rend les limites du littéral beaucoup moins susceptibles de devenir ambiguës.

Les deux caractéristiques ci—dessus font des littéraux multilignes un excellent outil pour définir du HTML en ligne — par exemple dans une forme quelconque d’outil de génération de pages Web, ou lors du rendu de parties du contenu d’une application à l’aide de vues Web – comme ceci:

extension Article { var html: String { // If we want to break a multiline literal into separate // lines without causing an *actual* line break, then we // can add a trailing '\' to one of our lines. let twitterLink = """ <a href="https://twitter.com/\(author.twitterHandle)">\ @\(author.twitterHandle)</a> """ return """ <article> <h1>\(title)</h1> <div class="author"> <p>\(author.name)</p> \(twitterLink) </div> <div class="body">\(body)</div> </article> """ }}

La technique ci-dessus peut également être très utile lors de la définition de données de test basées sur des chaînes. Par exemple, disons que les paramètres de notre application doivent être exportables en XML, et que nous voulons écrire un test qui vérifie cette fonctionnalité. Plutôt que d’avoir à définir le XML que nous voulons vérifier dans un fichier séparé, nous pouvons utiliser un littéral de chaîne multiligne pour l’insérer dans notre test:

class SettingsTests: XCTestCase { func testXMLConversion() { let settings = Settings( messageLimit: 7, enableSync: true, signature: "Sent from my Swift app" ) XCTAssertEqual(settings.xml, """ <?xml version="1.0" encoding="UTF-8"?> <settings> <messagelimit>7</messagelimit> <enablesync>1</enablesync> <signature>Sent from my Swift app</signature> </settings> """) }}

L’avantage de définir des données de test en ligne, comme nous le faisons ci—dessus, est qu’il devient beaucoup plus facile de repérer rapidement les erreurs commises lors de l’écriture du test — puisque le code de test et la sortie attendue sont placés juste à côté l’un de l’autre. Cependant, si certaines données de test dépassent une poignée de lignes de longueur, ou si les mêmes données doivent être utilisées à plusieurs endroits, il peut toujours être utile de les déplacer dans son propre fichier.

Chaînes brutes

Nouveauté dans Swift 5, les chaînes brutes nous permettent de désactiver toutes les fonctionnalités littérales de chaînes dynamiques (telles que l’interpolation et l’interprétation de caractères spéciaux, comme \n), au profit d’un simple traitement d’un littéral comme une séquence brute de caractères. Les chaînes brutes sont définies en entourant un littéral de chaîne avec des signes de dièse (ou « hashtags », comme les enfants les appellent):

let rawString = #"Press "Continue" to close this dialog."#

Tout comme nous avons utilisé ci—dessus un littéral multiligne pour définir les données de test, les littéraux de chaînes brutes sont particulièrement utiles lorsque nous voulons aligner des chaînes qui doivent contenir des caractères spéciaux – tels que des guillemets ou des barres obliques inverses. Voici un autre exemple lié au test, dans lequel nous utilisons un littéral de chaîne brute pour définir une chaîne JSON pour encoder une instance User de:

class UserTests: XCTestCase { func testDecoding() throws { let json = #"{"id": 37, "name": "John"}"# let data = Data(json.utf8) let user = try data.decoded() as User XCTAssertEqual(user.id, 37) XCTAssertEqual(user.name, "John") }}

Ci-dessus, nous utilisons l’API de décodage basée sur l’inférence de type de « Sérialisation alimentée par l’inférence de type dans Swift ».

Alors que les chaînes brutes désactivent par défaut des fonctionnalités telles que l’interpolation des chaînes, il existe un moyen de les remplacer en ajoutant un autre signe dièse juste après la barre oblique inverse de début de l’interpolation — comme ceci:

extension URL { func html(withTitle title: String) -> String { return #"<a href="\#(absoluteString)">\#(title)</a>"# }}

Enfin, les chaînes brutes sont également particulièrement utiles lors de l’interprétation d’une chaîne en utilisant une syntaxe spécifique, surtout si cette syntaxe repose fortement sur des caractères qui devraient normalement être échappés dans un littéral de chaîne — comme les expressions régulières. En définissant des expressions régulières à l’aide de chaînes brutes, aucun échappement n’est nécessaire, ce qui nous donne des expressions aussi lisibles qu’elles le sont:

// This expression matches all words that begin with either an// uppercase letter within the A-Z range, or with a number.let regex = try NSRegularExpression( pattern: #"(()|(\d))\w+"#)

Même avec les améliorations ci—dessus, on peut se demander à quel point les expressions régulières sont faciles à lire (et à déboguer), en particulier lorsqu’elles sont utilisées dans le contexte d’un langage hautement sécurisé comme Swift. Cela dépendra très probablement de l’expérience antérieure de tout développeur avec les expressions régulières, qu’il les préfère ou non à la mise en œuvre d’algorithmes d’analyse de chaînes plus personnalisés, directement dans Swift.

Exprimer des valeurs en utilisant des littéraux de chaîne

Bien que tous les littéraux de chaîne soient transformés en valeurs String par défaut, nous pouvons également les utiliser pour exprimer des valeurs personnalisées. Comme nous l’avons vu dans « Identificateurs de type sûrs dans Swift », l’ajout d’un support littéral de chaîne à l’un de nos propres types peut nous permettre d’obtenir une sécurité de type accrue, sans sacrifier la commodité d’utiliser des littéraux.

Par exemple, disons que nous avons défini un protocole Searchable pour servir d’API pour rechercher tout type de base de données ou de stockage sous—jacent utilisé par notre application – et que nous utilisons une énumération Query pour modéliser différentes façons d’effectuer une telle recherche:

protocol Searchable { associatedtype Element func search(for query: Query) -> }enum Query { case matching(String) case notMatching(String) case matchingAny()}

L’approche ci—dessus nous donne beaucoup de puissance et de flexibilité quant à la façon dont nous effectuerons chaque recherche, mais le cas d’utilisation le plus courant est toujours le plus simple — rechercher des éléments correspondant à une chaîne donnée – et ce serait vraiment bien si nous pouvions le faire en utilisant un littéral de chaîne.

La bonne nouvelle est que nous pouvons y arriver, tout en gardant l’API ci-dessus complètement intacte, en rendant Query conforme à ExpressibleByStringLiteral:

extension Query: ExpressibleByStringLiteral { init(stringLiteral value: String) { self = .matching(value) }}

De cette façon, nous sommes maintenant libres d’effectuer des recherches correspondantes sans avoir à créer manuellement une valeur Query — tout ce que nous devons faire est de passer un littéral de chaîne comme si l’API que nous appelons acceptait directement un String. Ici, nous utilisons cette fonctionnalité pour implémenter un test qui vérifie qu’un type UserStorage implémente correctement sa fonctionnalité de recherche:

class UserStorageTests: XCTestCase { func testSearch() { let storage = UserStorage.inMemory let user = User(id: 3, name: "Amanda") storage.insert(user) let matches = storage.search(for: "anda") XCTAssertEqual(matches, ) }}

Les expressions littérales de chaîne personnalisées peuvent dans de nombreuses situations nous éviter d’avoir à choisir entre la sécurité du type et la commodité lorsque vous travaillez avec des types basés sur des chaînes, tels que les requêtes et les identifiants. Il peut s’agir d’un excellent outil à utiliser pour obtenir une conception d’API qui évolue bien du cas d’utilisation le plus simple, jusqu’à couvrir les cas de périphérie et offrir plus de puissance et de personnalisation en cas de besoin.

Interpolation personnalisée

Une chose que toutes les « saveurs » des littéraux de chaîne Swift ont en commun est leur support pour les valeurs d’interpolation. Bien que nous ayons toujours été en mesure de personnaliser la façon dont un type donné est interpolé en nous conformant à CustomStringConvertible — Swift 5 introduit de nouvelles façons d’implémenter des API personnalisées directement au-dessus du moteur d’interpolation de chaînes.

À titre d’exemple, disons que nous voulons enregistrer une chaîne donnée en lui appliquant éventuellement un préfixe et un suffixe. Idéalement, nous aimerions simplement interpoler ces valeurs pour former la chaîne finale, comme ceci:

func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(prefix)\(text)\(suffix)" textStorage.store(text)}

Cependant, comme les prefix et suffix sont tous deux optionnels, le simple fait d’utiliser leur description ne produira pas le résultat que nous recherchons — et le compilateur nous donnera même un avertissement:

String interpolation produces a debug description for an optional value

Bien que nous ayons toujours la possibilité de déballer chacune de ces deux options avant de les interpoler, examinons comment nous pourrions faire ces deux choses en une seule fois en utilisant une interpolation personnalisée. Nous commencerons par étendre String.StringInterpolation avec une nouvelle surcharge appendInterpolation qui accepte toute valeur optionnelle:

extension String.StringInterpolation { mutating func appendInterpolation<T>(unwrapping optional: T?) { let string = optional.map { "\($0)" } ?? "" appendLiteral(string) }}

L’étiquette de paramètre ci—dessus unwrapping: est importante, car c’est ce que nous utiliserons pour indiquer le paramètre unwrapping: compilateur pour utiliser cette méthode d’interpolation spécifique – comme ceci:

func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(unwrapping: prefix)\(text)\(unwrapping: suffix)" textStorage.store(text)}

Bien que ce ne soit que du sucre syntaxique, ce qui précède a l’air vraiment soigné! Cependant, cela gratte à peine la surface de ce que les méthodes d’interpolation de chaînes personnalisées peuvent faire. Ils peuvent être à la fois génériques et non génériques, accepter n’importe quel nombre d’arguments, utiliser des valeurs par défaut et à peu près tout ce que les méthodes « normales » peuvent faire.

Voici un autre exemple dans lequel nous permettons à notre méthode de conversion d’une URL en lien HTML d’avant d’être également utilisée dans le contexte de l’interpolation de chaînes:

extension String.StringInterpolation { mutating func appendInterpolation(linkTo url: URL, _ title: String) { let string = url.html(withTitle: title) appendLiteral(string) }}

Avec ce qui précède en place, nous pouvons maintenant facilement générer des liens HTML à partir d’une URL comme celle-ci:

webView.loadHTMLString( "If you're not redirected, \(linkTo: url, "tap here").", baseURL: nil)

Ce qui est cool avec l’interpolation de chaînes personnalisées, c’est comment le compilateur prend chacune de nos méthodes appendInterpolation et les traduit en API d’interpolation correspondantes — nous donnant un contrôle complet sur l’apparence du site d’appel, par exemple en supprimant les étiquettes de paramètres externes, comme nous l’avons fait pour title ci-dessus.

Nous allons continuer à chercher d’autres façons d’utiliser l’interpolation de chaîne personnalisée, par exemple avec des chaînes attribuées et d’autres types de métadonnées de texte, dans les prochains articles.

Soutenez Swift by Sundell en consultant ce sponsor:

Développeur essentiel

Développeur essentiel: Rejoignez un cours intensif en ligne gratuit pour les développeurs iOS qui souhaitent devenir des développeurs seniors ceinture noire – c’est—à-dire: atteindre un niveau expert de compétences pratiques et faire partie des développeurs les mieux payés au monde.

Conclusion

Bien que certaines des capacités littérales de chaîne les plus avancées de Swift ne soient vraiment utiles que dans des situations très spécifiques, telles que celles de cet article, il est agréable de les avoir disponibles en cas de besoin — d’autant plus qu’il est possible de les éviter complètement et d’utiliser uniquement des chaînes "the old-fashioned way".

String literals est un autre domaine dans lequel la conception orientée protocole de Swift brille vraiment. En déléguant une grande partie de la façon dont les littéraux sont interprétés et gérés aux implémenteurs de protocoles, plutôt que de coder en dur ces comportements dans le compilateur lui-même, nous, développeurs tiers, sommes en mesure de personnaliser fortement la façon dont les littéraux sont gérés – tout en gardant les valeurs par défaut aussi simples qu’elles peuvent l’être.