Interpolação avançada de strings no Swift 5.0



A interpolação de cadeias estava no Swift de versões anteriores, mas no Swift 5.0 essa funcionalidade foi expandida, tornou-se mais rápida e muito mais poderosa.

Neste artigo, abordaremos as novas possibilidades de interpolação de cadeias e consideraremos como isso pode ser aplicado em nosso próprio código. Você também pode baixar as fontes deste artigo aqui.

O básico


Usamos interpolação básica de strings como esta:

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

Tomamos isso como garantido, mas ao mesmo tempo foi um alívio significativo em comparação com o que tínhamos de lidar antes:

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

Há também um ganho de desempenho significativo, pois a alternativa foi:

 let all = s1 + s2 + s3 + s4 

Sim, o resultado final seria o mesmo, mas Swift precisaria adicionar s1 a s2 para obter s5, adicionar s5 a s3 para obter s6 e adicionar s6 a s4 para obter s7, antes de atribuir tudo.

A interpolação de strings mal mudou com o Swift 1.0, a única mudança significativa ocorreu com o Swift 2.1, onde tivemos a oportunidade de usar literais de strings na interpolação:

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

Como você sabe, o Swift está se desenvolvendo em grande parte graças às sugestões da comunidade. As idéias são discutidas, desenvolvidas e aceitas ou rejeitadas.

Então, cinco anos depois, o desenvolvimento de Swift chegou à interpolação de linha. O Swift 5.0 apresenta novos super recursos que nos permitem controlar o processo de interpolação de strings.

Para tentar, considere o seguinte cenário. Se definirmos uma nova variável inteira como esta:

 let age = 38 

é óbvio que podemos usar a interpolação de strings da seguinte maneira:

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

Mas e se quisermos formatar o resultado de uma maneira diferente?

Usando o novo sistema de interpolação de strings no Swift 5.0, podemos escrever a extensão String.StringInterpolation para adicionar nosso próprio método de interpolação:

 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) } } } 

Agora, o código exibirá toda a variável como texto: "Oi, tenho trinta e oito".

Podemos usar uma técnica semelhante para corrigir a formatação da data, pois a exibição de data padrão como uma sequência não é muito atraente:

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

Você verá que o Swift exibe a data atual na forma de algo como: "2019-02-21 23:30:21 +0000". Podemos torná-lo mais bonito usando nossa própria formatação de data:

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

Agora, o resultado parece muito melhor, algo como: "21 de fevereiro de 2019 às 23:30:21".

Nota: para evitar possíveis confusões ao trabalhar juntos em equipe, você provavelmente não deve substituir os métodos Swift padrão. Portanto, forneça aos parâmetros os nomes de sua escolha para evitar confusão:

 mutating func appendInterpolation(format value: Int) { 

Agora vamos chamar esse método com um parâmetro nomeado:

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

Agora ficará claro que estamos usando nossa própria implementação do método.

Interpolação com parâmetros


Essa alteração mostra que agora temos controle total sobre como ocorre a interpolação de strings.

Por exemplo, podemos reescrever o código para processar as mensagens do Twitter:

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

Agora podemos escrever assim:

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

Mas por que devemos nos limitar a um parâmetro? Para o nosso exemplo de formatação de números, não faz sentido forçar os usuários a usar um parâmetro de conversão (.spellOut) - portanto, mudaremos o método adicionando um 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) } } 

E use-o assim:

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

Você pode ter quantos parâmetros quiser, de qualquer tipo. Exemplo usando @autoclosure para o valor padrão:

 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").") 

Usar o atributo @autoclosure significa que, para o valor padrão, podemos usar valores simples ou chamar funções complexas. No método, eles se tornarão um fechamento.

Agora, você pode estar pensando que podemos reescrever o código sem usar a funcionalidade de interpolação, algo como isto:

 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")).") 

Mas agora complicamos a ligação - porque obviamente estamos tentando formatar algo, esse é o ponto de interpolação. Lembre-se da regra Swift - evite palavras desnecessárias.

Erica Sadun ofereceu um exemplo muito breve e bonito de como simplificar o 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 ? "(*)" : "")") 

Adicionando interpolação de string para tipos personalizados


Podemos usar a interpolação de strings para nossos próprios 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)") 

A interpolação de cadeias de caracteres é útil porque não tocamos nas informações de depuração sobre um objeto. Se olharmos para ele no depurador ou o exibirmos, veremos dados intocados:

 print(hater) 

Podemos combinar um tipo personalizado com vários 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)") 

Obviamente, você pode usar todos os recursos do Swift para criar sua própria formatação. Por exemplo, podemos escrever uma implementação que aceite qualquer Encodable e a imprima em 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) } } 


Se tornarmos Person compatível com o protocolo Encodable , podemos fazer o seguinte:

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

Você pode usar recursos como um número variável de parâmetros, até marcar sua implementação de interpolação como lançada . Por exemplo, nosso sistema de formatação JSON não responde no caso de um erro de codificação, mas podemos corrigir isso para analisar o erro no 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)") 

Tudo o que vimos até agora são apenas modificações nos métodos de interpolação de strings.

Criando tipos personalizados usando a interpolação


Como você viu, era uma questão de como formatar os dados em seu aplicativo de uma maneira realmente conveniente, mas também podemos criar nossos próprios tipos usando a interpolação de strings.

Para demonstrar isso, criaremos um novo tipo que é inicializado a partir de string usando interpolação de string.

 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)" 

De fato, há um açúcar sintático sob o capô. Poderíamos escrever a 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) 

Conclusão


Como você viu, a interpolação de string personalizada nos permite colocar a formatação em um único local, para que as chamadas de método se tornem mais simples e claras. Também nos oferece uma grande flexibilidade para criar os tipos necessários da maneira mais natural possível.

Lembre-se de que essa é apenas uma das possibilidades - e não a única. Isso significa que algumas vezes usamos interpolação, outras funções ou algo mais. Como muito no desenvolvimento, você sempre precisa escolher a melhor maneira de resolver o problema.

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


All Articles