Um dos pontos que mais confundem quem migra de outra linguagem para Swift são as strings. text[0] não funciona, text[2..<5] também não, e é preciso criar um tipo de índice separado para escrever text.index(text.startIndex, offsetBy: 2). Em Python, isso seria resolvido com um único caractere.
Isso não aconteceu porque a equipe do Swift não conseguiu criar a API. É justamente o contrário: ela expõe com honestidade que o conceito de “enésimo caractere de uma string” não é simples. Outras linguagens escondem essa complexidade e às vezes retornam a resposta errada. O Swift escolheu sempre retornar a resposta certa, mesmo sendo menos conveniente. Nesta sexta e última parte da série Swift básico, vamos investigar por que strings são realmente difíceis.
O fluxo que pode falhar ao converter uma string em número é tratado com seu modelo de erro em Como escolher entre throws, try e Result no Swift.
Por que “quantos caracteres?” é difícil — o mundo do Unicode
Começamos com esta pergunta: quantos caracteres há em “café”? Parece óbvio que são 4, mas para o computador existem duas respostas. é pode ser armazenado como um único ponto de código precomposto (U+00E9), ou como e (U+0065) mais um acento combinante (U+0301). A aparência é igual, mas a quantidade interna de pontos de código é 1 ou 2.
O coreano torna esse problema ainda mais concreto. Uma sílaba coreana pode ser um único ponto de código de sílaba precomposta (U+AC01), ou uma combinação de três letras jamo. Se você já trabalhou com nomes de arquivos do macOS, talvez tenha visto nomes coreanos salvos em NFD (Normalization Form D — forma de normalização Unicode que decompõe letras em unidades jamo) aparecerem separados em outro sistema. O mesmo “caractere” pode ter um ou três pontos de código.
Os emojis acrescentam outra camada. O emoji de família 👨👩👧👦 une quatro emojis de pessoas com joiners de largura zero (ZWJ, Zero Width Joiner — caracteres invisíveis que agrupam emojis como um só), totalizando sete pontos de código. Em unidades de código UTF-16, são 11. Para os olhos, porém, é um caractere.
Por isso o padrão Unicode define uma unidade própria para “um caractere percebido por uma pessoa”: o cluster de grafemas estendido (extended grapheme cluster). é, uma sílaba coreana composta e o emoji de família são, cada um, um cluster de grafemas.
Escolhas das linguagens — uma resposta errada é mais rápida ou uma resposta certa é mais lenta?
Agora podemos comparar como cada linguagem lida com o “enésimo caractere”.
O "👨👩👧👦".length do JavaScript é 11 porque conta unidades de código UTF-16. Java funciona da mesma forma. O len do Python 3 retorna 7 porque conta pontos de código. Todos entram em conflito com a intuição humana de “um caractere”. Cortar ingenuamente uma string com emojis pode dividir um emoji ao meio e gerar texto corrompido.
O "👨👩👧👦".count do Swift é 1 porque Character representa um cluster de grafemas, não um ponto de código. count é igual independentemente de como é armazenado, e == retorna true considerando a normalização. A resposta é sempre correta segundo o que as pessoas veem.
Essa precisão tem um custo. Clusters de grafemas têm tamanho variável, então encontrar o enésimo caractere exige percorrer desde o início e verificar cada limite. Por isso strings do Swift não têm índices inteiros. Uma API que fizesse text[7] parecer O(1) esconderia um custo real de O(n). Usar text[i] dentro de um loop o torna O(n²), e o Swift decidiu não incentivar isso. O tipo opaco String.Index deixa claro pelo tipo que a posição foi encontrada por contagem. O princípio da primeira parte da série de filosofia — não esconder custos — também se repete nas strings.
Caixa de ferramentas prática — trabalhando com strings sem índices
Com o princípio entendido, vamos à prática. Mesmo sem índices inteiros, ferramentas mais seguras resolvem a maioria das tarefas.
Para cortar as extremidades, use prefix e suffix. text.prefix(10)retornam com segurança os 10 primeiros caracteres (grafemas), ou apenas os disponíveis, sem crash. Não é preciso calcular índices de substring para criar textos de pré-visualização.
Para pesquisar, use range(of:) e firstIndex(of:). Quando você precisa de uma posição, geralmente é a posição de um conteúdo específico. Basta colocar o Range ou Index retornado no subscript; não é necessária conversão para inteiro.
Para separar, use split. text.split(separator: ",")substitui a iteração manual por índices.
Se você realmente precisa do enésimo caractere, use Array. Quando um algoritmo precisa de acesso aleatório por caractere, o padrão é converter para Array(text). O custo O(n) da conversão é pago uma vez; depois, todo acesso custa O(1). É muito melhor do que chamar index(offsetBy:) a cada loop.
Quando precisar de bytes, especifique a view. Às vezes você precisa de quantidade de bytes, não de caracteres, como em tráfego de rede ou limites de colunas de DB. Swift deixa a unidade explícita com text.utf8.count, text.utf16.count e text.unicodeScalars.count. Lembre que NSString.length, encontrado em UserDefaults ou APIs antigas, usa UTF-16 e pode ser diferente de count do Swift.
Há uma armadilha: índices de strings diferentes não são compatíveis. Usar em b um String.Index obtido de a pode causar crash ou resultado incorreto. Um índice pertence àquela string naquele momento. Depois de modificar a string, considere os índices anteriores inválidos.
Por que isso é especialmente importante para desenvolvedores coreanos
Ao criar serviços coreanos, esse conhecimento deixa de ser teoria e passa a ser prático.
Limites de tamanho de apelidos são um exemplo típico. Ao validar “até 10 caracteres”, se servidor (outra linguagem) e cliente (Swift) contarem em unidades diferentes, um apelido aceito pelo cliente pode ser rejeitado pelo servidor. Se o servidor limitar bytes UTF-8, o coreano usa 3 bytes por caractere; portanto, 10 caracteres são 30 bytes. Primeiro, a equipe deve concordar sobre o que contar; no Swift, escolha conscientemente count (grafemas) ou utf8.count (bytes).
A decomposição em jamo é outro problema. Por causa do NFD mencionado acima, strings coreanas vindas de sistemas externos podem parecer iguais e ter pontos de código diferentes. O == do Swift considera a normalização, então geralmente funciona, mas ao usar strings externas como chaves de dicionários baseados em hash ou passá-las para outra linguagem, é mais seguro normalizar explicitamente para NFC (Normalization Form C — forma que armazena jamo combinados em caracteres precompostos) com precomposedStringWithCanonicalMapping.
Para recursos como busca por consoante inicial em pesquisa e autocompletar, é preciso decompor grafemas em jamo; a view unicodeScalars é o ponto de partida. Entendendo views, o requisito vira “percorrer outra view”, não “precisar de uma biblioteca especial”.
Resultado confirmado na execução
No Apple Swift 6.3.3, comparamos a forma precomposta 한 com a forma composta criada com três jamo 한. A string composta tem três Unicode scalar, mas String.count a conta como um caractere, e as duas representações foram iguais na comparação de equivalência canônica.
string=characters:1,scalars:3,canonically-equal:true
Depois de confirmar isso, passei a mostrar a unidade nos nomes das variáveis ao implementar limites de tamanho. Para characterLimit, uso count; para tamanho de transferência, utf8.count, e registro a mesma unidade no contrato do servidor. Se o acordo usar apenas um length sem unidade, coreano e emojis podem facilmente causar divergências entre cliente e servidor.
Resumo
- O “enésimo caractere” é difícil porque um caractere Unicode (um cluster de grafemas) é formado por uma quantidade variável de pontos de código. é, coreano composto e emojis ZWJ são exemplos típicos.
- O length de outras linguagens pode contar unidades ou pontos de código e divergir da intuição. O count do Swift conta grafemas e sempre responde segundo a perspectiva humana.
- O preço é acesso aleatório O(n); por isso o Swift removeu índices inteiros para não esconder esse custo.
- Na prática, prefix/suffix, range(of:) e split resolvem quase tudo; use conversão para Array no acesso aleatório e especifique a view utf8 para contar bytes.
- Em serviços coreanos, os pontos práticos são concordar sobre a unidade de validação de caracteres e tratar a normalização NFC/NFD.
Com isso termina a série básica de seis partes. A partir de agora começa a série intermediária. O primeiro tema é o coração do gerenciamento de memória do Swift: ARC (Automatic Reference Counting) e a escolha entre weak e unowned. É a parte principal da história das referências cíclicas anunciada no artigo sobre closures.
Leitura recomendada
- [Swift básico #5] O mapa completo do tratamento de erros no Swift: quando usar throws, try?, try! ou Result
- [Swift básico #4] Como usar Swift guard corretamente: saídas antecipadas que derrubam a pirâmide da desgraça
- Padrão Coordinator no iOS: removendo o código de transição de telas dos view controllers
Fontes e verificação
- Swift StringApple Developer Documentation · Documentação oficial · Consultado 26 de agosto de 2026Evidência: Modelo de coleção Character de String, equivalência canônica Unicode, views de strings e APIs de índice
- The Swift Programming Language: Strings and CharactersSwift.org · Documentação oficial · Consultado 26 de agosto de 2026Evidência: Clusters de grafemas estendidos, representação coreana composta e custo de count e indexação inteira

![Imagem de capa de [Swift básico #6] Por que strings não aceitam text[0]?](/assets/images/posts/18529e8e-628f-4b41-8e48-680d2ff6f480/1.jpg)