Cadenas en Swift: ¿por qué substring no funciona como estás acostumbrado?

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: El "truco" que espera a cada desarrollador de Swift

\n\n

Imagina la situación: escribes código en Swift con confianza, todo va bien, y de repente necesitas tomar los primeros tres caracteres de una cadena. Sin pensarlo, escribes algo como myString[0..<3] — y obtienes... un error de compilación. ¿Te suena familiar?

\n\n

Bienvenido al mundo de String substring en Swift. Es uno de esos temas que causa un verdadero dolor a los principiantes e incluso a desarrolladores experimentados que vienen de otros lenguajes. La pregunta "How does String substring work in Swift" en Stack Overflow ha acumulado miles de visitas — y no es casualidad.

\n\n

En este artículo analizaremos por qué Swift es tan "extraño" al trabajar con subcadenas, cómo usar correctamente substring en Swift, y qué nuevas posibilidades han aparecido en las versiones recientes. ¡Allá vamos!

\n\n

¿Por qué Swift no permite simplemente cortar un trozo de cadena?

\n\n

En la mayoría de los lenguajes (Python, JavaScript, C#) una cadena es simplemente un array de caracteres. ¿Quieres una subcadena? Escribe str[0:3] y obtén el resultado. En Swift es diferente.

\n\n

La razón está en el trabajo con Unicode. Las cadenas en Swift no son solo bytes o caracteres ASCII. Están compuestas de clústeres de grafemas (grapheme clusters). Un solo carácter que ve el usuario (por ejemplo, "é" o "👍🏽") puede ocupar varios Character o incluso varios Unicode.Scalar. Imagina que cortas una cadena en medio de uno de esos clústeres — obtendrías datos corruptos.

\n\n

Por eso Swift utiliza índices (String.Index), en lugar de números enteros. Esto garantiza que nunca dividirás un carácter por la mitad.

\n\n

Fundamentos: String.Index y extracción simple de subcadenas

\n\n

Comencemos con un ejemplo básico. Supongamos que tenemos una cadena:

\n\n
let fullString = "¡Hola, Swift!"\n// Queremos tomar la palabra "Swift"\nlet startIndex = fullString.index(fullString.startIndex, offsetBy: 6)\nlet endIndex = fullString.index(fullString.startIndex, offsetBy: 11)\nlet substring = fullString[startIndex..<endIndex]\nprint(substring) // Imprime: "Swift"
\n\n

Observa: substring tiene el tipo Substring, no String. Esta es una diferencia importante. Substring es una "ventana" a la cadena original. No copia los datos, simplemente hace referencia a una parte de la memoria de la cadena original. Esto es súper eficiente, pero puede provocar fugas de memoria si la cadena original es grande y la subcadena es pequeña.

\n\n

Para convertir un Substring en un String completo, simplemente usa el inicializador:

\n\n
let newString = String(substring)\nprint(newString) // "Swift"
\n\n

Azúcar sintáctico: RangeExpression

\n\n

Escribir index(offsetBy:) cada vez es tedioso. Afortunadamente, Swift permite usar formas más cortas con RangeExpression.

\n\n

Operador ... (ClosedRange)

\n
let str = "¡Hola, Mundo!"\nif let range = str.range(of: "Mundo") {\n    let mundoSubstring = str[range]\n    print(mundoSubstring) // "Mundo"\n}
\n\n

Rangos parciales (Partial Range)

\n

¿Quieres tomar todo hasta un índice determinado o después de él? Fácil:

\n
let text = "Swift Programación"\nlet prefix = text[..<text.index(text.startIndex, offsetBy: 5)]\nprint(prefix) // "Swift"\n\nlet suffix = text[text.index(text.startIndex, offsetBy: 6)...]\nprint(suffix) // "Programación"
\n\n

Observa: prefix y suffix también son Substring.

\n\n

Métodos incorporados: prefix, suffix, dropFirst, dropLast

\n\n

Swift proporciona métodos convenientes que evitan el cálculo manual de índices. Devuelven Substring.

\n\n