Literais de String em Swift
ser capaz de expressar valores básicos, tais como strings e inteiros, usando literais inline é uma característica essencial na maioria das linguagens de programação. No entanto, enquanto muitas outras línguas têm o apoio literais, cozido em seu compilador, Swift leva de uma forma muito mais dinâmica de abordagem — usando o seu próprio tipo de sistema para definir o modo como os vários literais devem ser manipulados, através de protocolos.
Esta semana, vamos focar em literais de string em particular, dando uma olhada nas muitas maneiras diferentes que eles podem ser usados e como nós — através do design altamente orientado a protocolo do Swift-somos capazes de personalizar a forma como os literais são interpretados, o que nos permite fazer algumas coisas realmente interessantes.
Programador essencial: Junte – se a um curso de colisão online gratuito para os programadores iOS que queiram tornar-se programadores seniores de cinturão negro — isto é: alcançar um nível de expert de habilidades práticas e se tornar parte dos desenvolvedores mais bem pagos do mundo.
os conceitos básicos
tal como em muitas outras línguas, as cadeias Swift são expressas através de literais rodeadas por aspas-e podem conter ambas as sequências especiais( como as linhas novas), caracteres fugitivos e valores interpolados:
let string = "\(user.name) says \"Hi!\"\nWould you like to reply?"// John says "Hi!"// Would you like to reply?
Enquanto os recursos usados acima já nos fornecem muita flexibilidade, e são provavelmente suficiente para a grande maioria de casos de uso, existem situações em que mais poderosas formas de expressar literais pode vir a calhar. Vamos dar uma olhada em alguns desses, começando com quando precisamos definir uma string contendo várias linhas de texto.
literais multi-linhas
embora qualquer texto literal padrão possa ser dividido em várias linhas usando \n
, isso nem sempre é prático — especialmente se estamos olhando para definir um pedaço maior de texto como um literal inline.
felizmente, desde Swift 4, também somos capazes de definir literais multi-linhas usando três aspas em vez de apenas uma. Por exemplo, aqui estamos usando essa capacidade para enviar um texto de Ajuda para um script Swift, no caso de o Usuário não passar quaisquer argumentos ao invocá-lo na linha de comando:
// 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)}
acima fazemos uso do fato de que os literais de cadeia múltipla preservam a indentação do seu texto, em relação ao conjunto de aspas terminantes, no fundo. Eles também nos permitem usar muito mais livremente aspas sem ocultação dentro deles, uma vez que eles são definidos por um conjunto de três aspas, tornando os limites do literal muito menos propensos a se tornar Ambíguo.
ambas as duas características acima tornam os literais multi-linhas uma grande ferramenta para definir HTML — por exemplo, em alguma forma de Ferramenta de geração de páginas web, ou quando renderizar partes do conteúdo de uma aplicação usando vistas web — como este:
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> """ }}
a técnica acima também pode ser realmente útil ao definir dados de teste baseados em string. Por exemplo, digamos que as configurações do nosso aplicativo precisam ser exportáveis como XML, e que queremos escrever um teste que verifique essa funcionalidade. Ao invés de ter que definir o XML que queremos verificar contra em um arquivo separado — podemos usar de várias linhas literal de cadeia de caracteres embutido em nosso teste:
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> """) }}
O benefício de definição de dados de teste embutido, como fizemos acima, é que se torna muito mais fácil para rapidamente detectar eventuais erros cometidos ao escrever o teste — desde o código de teste e o resultado esperado são colocadas uma ao lado da outra. No entanto, se alguns dados de teste exceder um punhado de linhas de comprimento, ou se os mesmos dados precisam ser usados em vários locais, ainda pode valer a pena movê-lo para seu próprio arquivo.strings Raw
New in Swift 5, raw strings enable us to turn off all dynamic string literal features (such as interpolation, and interpreting special characters, like \n
), in favor of simply treating a literal as a raw sequence of characters. Strings Raw são definidas rodeando um string literal com signos de libra (ou “hashtags”, como as crianças os chamam):
let rawString = #"Press "Continue" to close this dialog."#
assim como nós acima usamos um literal multi — linhas para definir dados de teste, literais de string raw são particularmente úteis quando queremos strings inline que precisam conter caracteres especiais-tais como aspas ou backslashes. Aqui está outro exemplo relacionado ao teste, no qual usamos um literal de cadeia raw para definir uma cadeia JSON para codificar uma User
instância 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") }}
acima usamos a API de decodificação baseada em inferência do tipo “Type inference-powered serialization in Swift”.
Enquanto matérias cadeias desativar recursos como a seqüência de interpolação por padrão, não há uma forma de substituição que, adicionando um sinal de libra logo após a interpolação líder de barra invertida como este:
extension URL { func html(withTitle title: String) -> String { return #"<a href="\#(absoluteString)">\#(title)</a>"# }}
Finalmente, matérias strings também são particularmente úteis quando da interpretação de uma seqüência de caracteres usando-se uma sintaxe específica, especialmente se a sintaxe depende fortemente de caracteres que normalmente precisam ser encerradas dentro de um literal de cadeia de caracteres, tais como expressões regulares. Pela definição de expressões regulares com matérias cordas, sem escapar é necessária, dando-nos expressões que são tão legível como eles se:
// 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+"#)
Mesmo com as melhorias acima, é questionável o quão fácil de ler (e de depuração) as expressões regulares são, especialmente, quando usada no contexto de um altamente seguros língua, como o Swift. É mais provável que venha a ser a experiência anterior de qualquer desenvolvedor com expressões regulares, quer eles prefiram ou não a implementação de algoritmos de processamento de string mais personalizados, diretamente no Swift.
expressando valores usando string literals
enquanto todos os string literals são transformados em String
valores por padrão, também podemos usá-los para expressar valores personalizados. Como nós demos uma olhada em “Type-safe identifiers in Swift”, adicionando suporte literal de string para um de nossos próprios tipos pode nos deixar alcançar maior segurança de tipo, sem sacrificar a conveniência de usar literais.
Por exemplo, vamos dizer que temos definido um Searchable
protocolo para agir como a API para procurar qualquer tipo de banco de dados ou de armazenamento subjacente que o nosso aplicativo usa — e que estamos usando um Query
enum para o modelo de diferentes maneiras para realizar uma pesquisa:
protocol Searchable { associatedtype Element func search(for query: Query) -> }enum Query { case matching(String) case notMatching(String) case matchingAny()}
a abordagem acima nos dá um monte de poder e flexibilidade quanto a como vamos realizar cada busca, mas o caso de uso mais comum ainda é provável que seja o mais simples — procurar por elementos que correspondam a uma determinada cadeia — e seria realmente bom se fossemos capazes de fazer isso usando um texto literal.
A boa notícia é que podemos fazer isso acontecer, enquanto ainda mantendo a API acima completamente intacta, fazendoQuery
conformar-se com ExpressibleByStringLiteral
:
extension Query: ExpressibleByStringLiteral { init(stringLiteral value: String) { self = .matching(value) }}
de Que forma estamos agora livre para realizar a correspondência de pesquisas sem ter que criar uma Query
valor manualmente — tudo o que precisamos fazer é passar uma seqüência de caracteres literal, como se a API estamos chamando realmente aceitou um String
diretamente. Aqui estamos usando essa capacidade para implementar um teste que verifica que um UserStorage
tipo implementa corretamente sua funcionalidade de pesquisa:
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, ) }}
expressões literais personalizadas podem em muitas situações evitar ter que escolher entre a segurança do tipo e a conveniência quando se trabalha com tipos baseados em string, tais como consultas e identificadores. Pode ser uma grande ferramenta para usar, a fim de alcançar um design API que Escale bem desde o caso de uso mais simples, todo o caminho para cobrir casos de borda e oferecendo mais poder e customizabilidade quando necessário.
interpolação personalizada
uma coisa que todos os “sabores” dos literais de cordas Swift têm em comum é o seu suporte para a interpolação de valores. While we’ve always been able to customize how a Giv type is interpolated by conforming to CustomStringConvertible
— Swift 5 introduces new ways of implementing custom APIs right on top of the string interpolation engine.
Como exemplo, digamos que queremos salvar uma dada string, aplicando opcionalmente um prefixo e Sufixo a ela. Idealmente gostaríamos de simplesmente interpolar esses valores para formar a corda final, assim:
func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(prefix)\(text)\(suffix)" textStorage.store(text)}
no Entanto, uma vez que ambos prefix
e suffix
são opcionais, simplesmente usando a sua descrição não produzir o resultado que está procurando — e o compilador ainda vai nos dar um aviso:
String interpolation produces a debug description for an optional value
Enquanto, temos sempre a opção de aninhamento de cada um daqueles dois opcionais antes de interpolação-los, vamos dar uma olhada em como podemos fazer ambas as coisas de uma vez personalizado usando interpolação. Vamos começar pela extensão String.StringInterpolation
com um novo appendInterpolation
sobrecarga que aceita qualquer valor opcional:
extension String.StringInterpolation { mutating func appendInterpolation<T>(unwrapping optional: T?) { let string = optional.map { "\($0)" } ?? "" appendLiteral(string) }}
acima unwrapping:
rótulo do parâmetro é importante, como é que nós vamos usar para dizer ao compilador para uso específico método de interpolação, como este:
func save(_ text: String, prefix: String?, suffix: String?) { let text = "\(unwrapping: prefix)\(text)\(unwrapping: suffix)" textStorage.store(text)}
Embora seja apenas um açúcar sintático, acima parece muito legal! No entanto, isso mal coça a superfície do que os métodos de interpolação personalizados de cordas podem fazer. Eles podem ser genéricos e não-genéricos, aceitar qualquer número de argumentos, usar valores padrão, e praticamente qualquer outra coisa que os métodos “normais” podem fazer.
Aqui está outro exemplo no qual nós habilitamos nosso método para converter um URL em um link HTML de antes para também ser usado no contexto da interpolação de texto:
extension String.StringInterpolation { mutating func appendInterpolation(linkTo url: URL, _ title: String) { let string = url.html(withTitle: title) appendLiteral(string) }}
com o acima no lugar, nós podemos agora facilmente gerar links HTML a partir de um URL como este:
webView.loadHTMLString( "If you're not redirected, \(linkTo: url, "tap here").", baseURL: nil)
A coisa legal sobre como personalizar a cadeia de interpolação é a forma como o compilador leva a cada um de nossos appendInterpolation
métodos e converte-los em correspondente interpolação APIs — nos dar controle completo sobre o que o site de chamada vai olhar como, por exemplo, a remoção de parâmetro externo rótulos, como fizemos para title
acima.
continuaremos a procurar mais formas de usar interpolação personalizada de cadeias de caracteres, por exemplo com cadeias atribuídas e outros tipos de meta-dados de texto, nos próximos artigos.
Suporte Swift por Sundell, verificando este patrocinador:
Essencial Desenvolvedor: Junte-se a um curso intensivo para desenvolvedores iOS que querem tornar-black-belt sênior desenvolvedores — que é: alcançar um nível avançado de conhecimentos práticos e tornar-se parte do mais bem pagos de desenvolvedores em todo o mundo.
Conclusão
Enquanto alguns de Swift é mais avançadas de seqüência de caracteres literal recursos só são realmente úteis em situações muito específicas, como as que existem neste artigo, é bom tê-los disponíveis quando necessárias — especialmente desde que é possível evitá-las completamente e só usar seqüências de caracteres "the old-fashioned way"
.
String literals is another area in which Swift’s protocol-oriented design really shines. Ao delegar muito de como os literais são interpretados e tratados para implementadores de protocolos, ao invés de codificar esses comportamentos no compilador em si, nós, como desenvolvedores de terceiros, somos capazes de personalizar fortemente a forma como os literais são tratados-ao mesmo tempo mantendo os defaults tão simples quanto eles podem ser.