Interpolation de chaînes avancée dans Swift 5.0



L'interpolation de chaînes était dans Swift à partir des versions précédentes, mais dans Swift 5.0, cette fonctionnalité a été étendue, est devenue plus rapide et beaucoup plus puissante.

Dans cet article, nous allons passer en revue les nouvelles possibilités d'interpolation de chaînes et examiner comment cela peut être appliqué dans notre propre code. Vous pouvez également télécharger les sources de cet article ici.

Les bases


Nous utilisons une interpolation de chaîne de base comme ceci:

let age = 38 print("You are \(age)") 

Nous tenons cela pour acquis, mais à un moment donné, c'était un soulagement important par rapport à ce que nous avions à gérer auparavant:

 [NSString stringWithFormat:@"%ld", (long)unreadCount]; 

Il y a également un gain de performances significatif, car l'alternative était:

 let all = s1 + s2 + s3 + s4 

Oui, le résultat final serait le même, mais Swift devrait ajouter s1 à s2 pour obtenir s5, ajouter s5 à s3 pour obtenir s6 et ajouter s6 à s4 pour obtenir s7, avant de tout attribuer.

L'interpolation de chaînes n'a pratiquement pas changé avec Swift 1.0, le seul changement significatif est venu avec Swift 2.1, où nous avons eu l'occasion d'utiliser des littéraux de chaîne en interpolation:

 print("Hi, \(user ?? "Anonymous")") 

Comme vous le savez, Swift se développe en grande partie grâce aux suggestions de la communauté. Les idées sont discutées, développées et acceptées ou rejetées.

Ainsi, cinq ans plus tard, le développement de Swift est arrivé à l'interpolation de lignes. Swift 5.0 introduit de nouvelles super fonctionnalités qui nous permettent de contrôler le processus d'interpolation de chaînes.

Pour essayer, envisagez le scénario suivant. Si nous définissons une nouvelle variable entière comme celle-ci:

 let age = 38 

il est assez évident que nous pouvons utiliser l'interpolation de chaînes comme suit:

 print("Hi, I'm \(age).") 

Mais que se passe-t-il si nous voulons formater le résultat d'une manière différente?

En utilisant le nouveau système d'interpolation de chaînes dans Swift 5.0, nous pouvons écrire l'extension String.StringInterpolation pour ajouter notre propre méthode d'interpolation:

 extension String.StringInterpolation { mutating func appendInterpolation(_ value: Int) { let formatter = NumberFormatter() formatter.numberStyle = .spellOut if let result = formatter.string(from: value as NSNumber) { appendLiteral(result) } } } 

Maintenant, le code affichera la variable entière sous forme de texte: "Salut, j'ai trente-huit ans."

Nous pouvons utiliser une technique similaire pour corriger la mise en forme de la date, car la vue de date par défaut sous forme de chaîne n'est pas très attrayante:

 print("Today's date is \(Date()).") 

Vous verrez que Swift affiche la date actuelle sous la forme de quelque chose comme: «2019-02-21 23:30:21 +0000». Nous pouvons le rendre plus beau en utilisant notre propre formatage de date:

 mutating func appendInterpolation(_ value: Date) { let formatter = DateFormatter() formatter.dateStyle = .full let dateString = formatter.string(from: value) appendLiteral(dateString) } 

Maintenant, le résultat est bien meilleur, quelque chose comme: "21 février 2019 23:30:21".

Remarque: pour éviter toute confusion possible lorsque vous travaillez ensemble dans une équipe, vous ne devriez probablement pas remplacer les méthodes Swift par défaut. Par conséquent, donnez aux paramètres les noms de votre choix pour éviter toute confusion:

 mutating func appendInterpolation(format value: Int) { 

Maintenant, nous appellerons cette méthode avec un paramètre nommé:

 print("Hi, I'm \(format: age).") 

Maintenant, il sera clair que nous utilisons notre propre implémentation de la méthode.

Interpolation avec paramètres


Ce changement montre que nous avons maintenant un contrôle total sur la façon dont l'interpolation de chaîne se produit.

Par exemple, nous pouvons réécrire le code pour traiter les messages Twitter:

 mutating func appendInterpolation(twitter: String) { appendLiteral("<a href=\"https://twitter.com/\(twitter)\">@\(twitter)</a>") } 

Maintenant, nous pouvons écrire comme ceci:

 print("You should follow me on Twitter: \(twitter: "twostraws").") 

Mais pourquoi devrions-nous nous limiter à un seul paramètre? Pour notre exemple de mise en forme des nombres, cela n'a aucun sens de forcer les utilisateurs à utiliser un paramètre de conversion (.spellOut) - nous allons donc changer la méthode en ajoutant un deuxième paramètre:

 mutating func appendInterpolation(format value: Int, using style: NumberFormatter.Style) { let formatter = NumberFormatter() formatter.numberStyle = style if let result = formatter.string(from: value as NSNumber) { appendLiteral(result) } } 

Et utilisez-le comme ceci:

 print("Hi, I'm \(format: age, using: .spellOut).") 

Vous pouvez avoir autant de paramètres que vous le souhaitez, de n'importe quel type. Exemple utilisant @autoclosure pour la valeur par défaut:

 extension String.StringInterpolation { mutating func appendInterpolation(_ values: [String], empty defaultValue: @autoclosure () -> String) { if values.count == 0 { appendLiteral(defaultValue()) } else { appendLiteral(values.joined(separator: ", ")) } } } let names = ["Malcolm", "Jayne", "Kaylee"] print("Crew: \(names, empty: "No one").") 

L'utilisation de l'attribut @autoclosure signifie que pour la valeur par défaut, nous pouvons utiliser des valeurs simples ou appeler des fonctions complexes. Dans la méthode, ils deviendront une fermeture.

Maintenant, vous pensez peut-être que nous pouvons réécrire le code sans utiliser la fonctionnalité d'interpolation, quelque chose comme ceci:

 extension Array where Element == String { func formatted(empty defaultValue: @autoclosure () -> String) -> String { if count == 0 { return defaultValue() } else { return self.joined(separator: ", ") } } } print("Crew: \(names.formatted(empty: "No one")).") 

Mais maintenant, nous avons compliqué l'appel - parce que nous essayons évidemment de formater quelque chose, c'est le point d'interpolation. N'oubliez pas la règle Swift - évitez les mots inutiles.

Erica Sadun a offert un très bel et court exemple de la façon de simplifier le code:

 extension String.StringInterpolation { mutating func appendInterpolation(if condition: @autoclosure () -> Bool, _ literal: StringLiteralType) { guard condition() else { return } appendLiteral(literal) } } let doesSwiftRock = true print("Swift rocks: \(if: doesSwiftRock, "(*)")") print("Swift rocks \(doesSwiftRock ? "(*)" : "")") 

Ajout d'une interpolation de chaîne pour les types personnalisés


Nous pouvons utiliser l'interpolation de chaînes pour nos propres types:

 struct Person { var type: String var action: String } extension String.StringInterpolation { mutating func appendInterpolation(_ person: Person) { appendLiteral("I'm a \(person.type) and I'm gonna \(person.action).") } } let hater = Person(type: "hater", action: "hate") print("Status check: \(hater)") 

L'interpolation de chaînes est utile car nous n'abordons pas les informations de débogage sur un objet. Si nous le regardons dans le débogueur ou l'afficher, alors nous verrons des données intactes:

 print(hater) 

Nous pouvons combiner un type personnalisé avec plusieurs paramètres:

 extension String.StringInterpolation { mutating func appendInterpolation(_ person: Person, count: Int) { let action = String(repeating: "\(person.action) ", count: count) appendLiteral("\n\(person.type.capitalized)s gonna \(action)") } } let player = Person(type: "player", action: "play") let heartBreaker = Person(type: "heart-breaker", action: "break") let faker = Person(type: "faker", action: "fake") print("Let's sing: \(player, count: 5) \(hater, count: 5) \(heartBreaker, count: 5) \(faker, count: 5)") 

Bien sûr, vous pouvez utiliser toutes les fonctionnalités de Swift pour créer votre propre formatage. Par exemple, nous pouvons écrire une implémentation qui accepte tout Encodable et l'imprime en JSON:

 mutating func appendInterpolation<T: Encodable>(debug value: T) { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted if let result = try? encoder.encode(value) { let str = String(decoding: result, as: UTF8.self) appendLiteral(str) } } 


Si nous rendons Person conforme au protocole Encodable , nous pouvons le faire:

 print("Here's some data: \(debug: faker)") 

Vous pouvez utiliser des fonctionnalités comme un nombre variable de paramètres, allez même marquer votre implémentation d'interpolation comme lancée . Par exemple, notre système de formatage JSON ne répond pas en cas d'erreur de codage, mais nous pouvons corriger cela pour analyser l'erreur à l'avenir:

 mutating func appendInterpolation<T: Encodable>(debug value: T) throws { let encoder = JSONEncoder() encoder.outputFormatting = .prettyPrinted let result = try encoder.encode(value) let str = String(decoding: result, as: UTF8.self) appendLiteral(str) } print(try "Status check: \(debug: hater)") 

Tout ce que nous avons examiné jusqu'à présent ne sont que des modifications des méthodes d'interpolation de chaînes.

Création de types personnalisés à l'aide de l'interpolation


Comme vous l'avez vu, il s'agissait de savoir comment formater les données dans votre application de manière très pratique, mais nous pouvons également créer nos propres types en utilisant l'interpolation de chaînes.

Pour le démontrer, nous allons créer un nouveau type qui est initialisé à partir d'une chaîne à l'aide d'une interpolation de chaîne.

 struct ColoredString: ExpressibleByStringInterpolation { //    -    -    struct StringInterpolation: StringInterpolationProtocol { //   -    var output = NSMutableAttributedString() //     var baseAttributes: [NSAttributedString.Key: Any] = [.font: UIFont(name: "Georgia-Italic", size: 64) ?? .systemFont(ofSize: 64), .foregroundColor: UIColor.black] //   ,        init(literalCapacity: Int, interpolationCount: Int) { } // ,      mutating func appendLiteral(_ literal: String) { //   ,       print("Appending \(literal)") //    let attributedString = NSAttributedString(string: literal, attributes: baseAttributes) //     output.append(attributedString) } // ,          mutating func appendInterpolation(message: String, color: UIColor) { //     print("Appending \(message)") //        var coloredAttributes = baseAttributes coloredAttributes[.foregroundColor] = color //    -     let attributedString = NSAttributedString(string: message, attributes: coloredAttributes) output.append(attributedString) } } //    ,     let value: NSAttributedString //      init(stringLiteral value: String) { self.value = NSAttributedString(string: value) } //     init(stringInterpolation: StringInterpolation) { self.value = stringInterpolation.output } } let str: ColoredString = "\(message: "Red", color: .red), \(message: "White", color: .white), \(message: "Blue", color: .blue)" 

En fait, il y a un sucre syntaxique sous le capot. Nous pourrions écrire la dernière partie manuellement:

 var interpolation = ColoredString.StringInterpolation(literalCapacity: 10, interpolationCount: 1) interpolation.appendLiteral("Hello") interpolation.appendInterpolation(message: "Hello", color: .red) interpolation.appendLiteral("Hello") let valentine = ColoredString(stringInterpolation: interpolation) 

Conclusion


Comme vous l'avez vu, l'interpolation de chaînes personnalisée nous permet de placer la mise en forme en un seul endroit, de sorte que les appels de méthode deviennent plus simples et plus clairs. Il nous offre également une grande flexibilité pour créer les types requis aussi naturellement que possible.

N'oubliez pas que ce n'est qu'une des possibilités - et non la seule. Cela signifie que parfois nous utilisons l'interpolation, parfois des fonctions ou autre chose. Comme dans le développement, vous devez toujours choisir la meilleure façon de résoudre le problème.

Source: https://habr.com/ru/post/fr447586/


All Articles