
La interpolación de cadenas se realizó en Swift desde versiones anteriores, pero en Swift 5.0 esta funcionalidad se expandió, se hizo más rápida y significativamente más poderosa.
En este artículo, repasaremos las nuevas posibilidades de interpolación de cadenas y consideraremos cómo se puede aplicar esto en nuestro propio código. También puede descargar las fuentes de este artículo
aquí.Los fundamentos
Usamos una interpolación de cadena básica como esta:
let age = 38 print("You are \(age)")
Damos esto por sentado, pero en un momento fue un alivio significativo en comparación con lo que tuvimos que enfrentar antes:
[NSString stringWithFormat:@"%ld", (long)unreadCount];
También hay una ganancia de rendimiento significativa, ya que la alternativa era:
let all = s1 + s2 + s3 + s4
Sí, el resultado final sería el mismo, pero Swift tendría que agregar s1 a s2 para obtener s5, agregar s5 a s3 para obtener s6 y agregar s6 a s4 para obtener s7, antes de asignar todo.
La interpolación de cadenas prácticamente no cambió con Swift 1.0, el único cambio significativo vino con Swift 2.1, donde tuvimos la oportunidad de usar
literales de cadena en la interpolación:
print("Hi, \(user ?? "Anonymous")")
Como saben, Swift se está desarrollando en gran medida gracias a las sugerencias de la comunidad. Las ideas son discutidas, desarrolladas y aceptadas o rechazadas.
Entonces, cinco años después, el desarrollo de Swift llegó a la interpolación de línea. Swift 5.0 presenta nuevas súper características que nos dan la capacidad de controlar el proceso de interpolación de cadenas.
Para probar, considere el siguiente escenario. Si establecemos una nueva variable entera como esta:
let age = 38
es bastante obvio que podemos usar la interpolación de cadenas de la siguiente manera:
print("Hi, I'm \(age).")
Pero, ¿qué pasa si queremos formatear el resultado de una manera diferente?
Usando el nuevo sistema de interpolación de cadenas en Swift 5.0, podemos escribir la extensión String.StringInterpolation para agregar nuestro propio método de interpolación:
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) } } }
Ahora el código generará la variable completa como texto: "Hola, tengo treinta y ocho".
Podemos usar una técnica similar para arreglar el formato de fecha, ya que la vista de fecha predeterminada como una cadena no es muy atractiva:
print("Today's date is \(Date()).")
Verá que Swift muestra la fecha actual en forma de algo así como: “2019-02-21 23:30:21 +0000”. Podemos hacerlo más hermoso usando nuestro propio formato de fecha:
mutating func appendInterpolation(_ value: Date) { let formatter = DateFormatter() formatter.dateStyle = .full let dateString = formatter.string(from: value) appendLiteral(dateString) }
Ahora el resultado se ve mucho mejor, algo así como: "21 de febrero de 2019 23:30:21".
Nota: para evitar una posible confusión al trabajar juntos en un equipo, probablemente no debería anular los métodos Swift predeterminados. Por lo tanto, asigne a los parámetros los nombres que elija para evitar confusiones:
mutating func appendInterpolation(format value: Int) {
Ahora llamaremos a este método con un parámetro con nombre:
print("Hi, I'm \(format: age).")
Ahora quedará claro que estamos utilizando nuestra propia implementación del método.
Interpolación con parámetros.
Este cambio muestra que ahora tenemos control total sobre cómo ocurre la interpolación de cadenas.
Por ejemplo, podemos reescribir el código para procesar mensajes de Twitter:
mutating func appendInterpolation(twitter: String) { appendLiteral("<a href=\"https://twitter.com/\(twitter)\">@\(twitter)</a>") }
Ahora podemos escribir así:
print("You should follow me on Twitter: \(twitter: "twostraws").")
Pero, ¿por qué deberíamos limitarnos a un parámetro? Para nuestro ejemplo de formato de número, no tiene sentido obligar a los usuarios a usar un parámetro de conversión (.spellOut), por lo que cambiaremos el método agregando un segundo parámetro:
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) } }
Y úsalo así:
print("Hi, I'm \(format: age, using: .spellOut).")
Puede tener tantos parámetros como desee, de cualquier tipo. Ejemplo usando
@autoclosure para el valor predeterminado:
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").")
El uso del atributo
@autoclosure significa que para el valor predeterminado, podemos usar valores simples o llamar a funciones complejas. En el método, se convertirán en un cierre.
Ahora, puede estar pensando que podemos reescribir el código sin usar la funcionalidad de interpolación, algo como esto:
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")).")
Pero ahora hemos complicado la llamada, porque obviamente estamos tratando de formatear algo, este es el punto de interpolación. Recuerde la regla de Swift: evite palabras innecesarias.
Erica Sadun ofreció un ejemplo realmente corto y hermoso de cómo simplificar el código:
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 ? "(*)" : "")")
Agregar interpolación de cadenas para tipos personalizados
Podemos usar la interpolación de cadenas para nuestros propios tipos:
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)")
La interpolación de cadenas es útil porque no tocamos información de depuración sobre un objeto. Si lo miramos en el depurador o lo mostramos, entonces veremos datos intactos:
print(hater)
Podemos combinar un tipo personalizado con varios parámetros:
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)")
Por supuesto, puede usar todas las funciones de Swift para crear su propio formato. Por ejemplo, podemos escribir una implementación que acepte cualquier
Encodable y la imprima 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 hacemos que
Person cumpla con el protocolo
Encodable , entonces podemos hacer esto:
print("Here's some data: \(debug: faker)")
Puede usar características como un número variable de parámetros, incluso marcar su implementación de interpolación como
lanzamiento . Por ejemplo, nuestro sistema de formato JSON no responde en caso de un error de codificación, pero podemos solucionarlo para analizar el error en el futuro:
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)")
Todo lo que hemos visto hasta ahora son solo modificaciones a los métodos de interpolación de cadenas.
Crear tipos personalizados con interpolación
Como viste, se trataba de una forma de formatear los datos en tu aplicación de una manera realmente conveniente, pero también podemos crear nuestros propios tipos usando la interpolación de cadenas.
Para demostrar esto, crearemos un nuevo tipo que se inicializa desde una cadena mediante la interpolación de cadenas.
struct ColoredString: ExpressibleByStringInterpolation {
De hecho, hay un azúcar sintáctico debajo del capó. Podríamos escribir la parte final manualmente:
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)
Conclusión
Como viste, la interpolación de cadenas personalizada nos permite colocar el formato en un solo lugar, para que las llamadas a métodos sean más simples y claras. También nos proporciona una gran flexibilidad para crear los tipos necesarios de la forma más natural posible.
Recuerde que esta es solo una de las posibilidades, y no la única. Esto significa que a veces usamos interpolación, a veces funciones u otra cosa. Como mucho en el desarrollo, siempre debe elegir la mejor manera de resolver el problema.