26/01/2017
Si has trabajado con Swift, especialmente desde la versión 4, es muy probable que te hayas encontrado con una peculiaridad interesante: al extraer una porción de un String, no obtienes otro String, sino un tipo de dato llamado Substring. Esta decisión de diseño del equipo de Swift no es arbitraria; responde a una búsqueda constante de rendimiento y eficiencia en el manejo de la memoria. En este artículo, desglosaremos a fondo por qué existe esta diferencia, cómo te afecta como desarrollador y cuál es la forma correcta de trabajar con subcadenas en el ecosistema moderno de Swift.

La Diferencia Clave: Rendimiento y Memoria
Para entender el propósito de Substring, primero debemos recordar una característica fundamental de los String en Swift: son tipos por valor. Esto significa que cada vez que asignas un string a una nueva variable o lo pasas a una función, Swift crea una copia completa de su contenido. Si bien esto garantiza la seguridad y previsibilidad del código (nadie puede modificar tu string sin que te des cuenta), puede ser muy costoso en términos de rendimiento y memoria, especialmente cuando trabajas con cadenas de texto muy grandes.
Imagina que tienes una cadena de un millón de caracteres y solo necesitas trabajar con una pequeña porción de diez caracteres. En un modelo puramente de copia, extraer esa subcadena implicaría asignar nueva memoria y copiar esos diez caracteres. Ahora, imagina hacer esto miles de veces en un bucle. El impacto en el rendimiento sería notable.
Substring al Rescate: Una Vista Inteligente
Aquí es donde Substring entra en juego. Un Substring no es una copia. En su lugar, es una vista o una especie de "puntero inteligente" que hace referencia a una porción de la memoria del String original. No se realiza ninguna copia de los caracteres. Cuando creas un Substring, simplemente estás almacenando los índices de inicio y fin que delimitan la porción deseada dentro del String original.
Esto es increíblemente eficiente para manipulaciones rápidas y temporales. Puedes cortar, dividir y analizar cadenas de texto sin preocuparte por la sobrecarga de la creación de múltiples copias.
La Trampa de la Memoria: Esta eficiencia tiene un costo oculto. Mientras un
Substringexista, mantendrá una referencia alStringoriginal completo, impidiendo que el sistema libere esa memoria. Si extraes unSubstringde 10 caracteres de unStringde 1 GB, ese gigabyte completo permanecerá en memoria hasta que elSubstringsea destruido. Por esta razón, la regla de oro es: losSubstringson para uso temporal.
Cuando hayas terminado de manipular tu subcadena y necesites almacenarla a largo plazo (por ejemplo, en una propiedad de una clase o en una base de datos), debes convertirla explícitamente a un String. Este acto sí crea una copia, pero solo de la pequeña porción que necesitas, permitiendo que el String original masivo sea finalmente liberado de la memoria.
var textoLargo = "Hola, este es un texto de ejemplo en el playground." // ... manipulaciones ... let miSubstring = textoLargo.prefix(4) // Esto es un Substring, no un String // Cuando necesites guardarlo, conviértelo: let miStringFinal = String(miSubstring) // Ahora 'miStringFinal' es un String independienteEl Dilema de los Índices: ¿Por Qué no Usar Enteros (Int)?
Otro pilar fundamental en el manejo de Strings en Swift es su sistema de índices. Si vienes de otros lenguajes, podrías intentar acceder a un carácter usando un índice entero, como texto[3]. En Swift, esto generará un error de compilación. La razón es profunda y está ligada a la compatibilidad con Unicode.

Los String en Swift no son simples arreglos de caracteres de tamaño fijo. Están compuestos por clústeres de grafemas extendidos, que son la representación percibida por un humano de un "carácter". Un solo carácter visible, como un emoji (👨👩👧👦) o una letra con un acento (é), puede estar compuesto por múltiples escalares Unicode. Esto significa que los caracteres no tienen un tamaño uniforme en memoria.
Para determinar qué carácter se encuentra en una posición particular, Swift debe iterar sobre cada escalar Unicode desde el principio (o el final) de la cadena. Usar un índice entero sería engañoso y terriblemente ineficiente, ya que no representa un desplazamiento de memoria fijo. Por ello, Swift nos obliga a usar un tipo opaco y seguro: String.Index.
Cómo Obtener Subcadenas en Swift (4 y 5)
Con los conceptos de Substring y String.Index claros, veamos las formas modernas y recomendadas de extraer subcadenas. A partir de Swift 5, el método substring(with:) ha sido deprecado, favoreciendo el uso directo de subíndices con rangos, lo que hace el código más limpio y consistente con otras colecciones.
Obtener desde el Inicio de la Cadena
La forma más sencilla es usar el método prefix().
var str = "Hola, playground" let miSubstring = str.prefix(4) // miSubstring contiene "Hola"También puedes hacerlo manualmente creando un índice y usando un rango unilateral:
let indice = str.index(str.startIndex, offsetBy: 4) let miSubstringConRango = str[..<indice] // Contiene "Hola"Obtener desde el Final de la Cadena
De manera simétrica, el método suffix() es tu mejor aliado.
var str = "Hola, playground" let miSubstring = str.suffix(10) // miSubstring contiene "playground"Y su equivalente con rangos:
let indice = str.index(str.endIndex, offsetBy: -10) let miSubstringConRango = str[indice...] // Contiene "playground"Obtener un Rango Específico en el Medio
Aquí es donde el uso de rangos con String.Index brilla. Debes calcular tanto el índice de inicio como el de fin.

var str = "Hola, playground" let inicio = str.index(str.startIndex, offsetBy: 6) let fin = str.index(str.endIndex, offsetBy: -7) let rango = inicio..<fin let miSubstring = str[rango] // miSubstring contiene "play"Comparativa: Range vs. NSRange
Si alguna vez has interactuado con APIs de Objective-C (como las de expresiones regulares con NSRegularExpression), te habrás topado con NSRange. Es crucial entender su diferencia con el Range nativo de Swift.
| Característica | Range (Swift) | NSRange (Objective-C) |
|---|---|---|
| Tipo de Índice | Basado en String.Index (opaco y seguro) | Basado en Int (posición y longitud) |
| Manejo de Unicode | Totalmente compatible y fiable con caracteres complejos. | No es fiable. Cuenta unidades de código UTF-16, no caracteres visibles. |
| Uso Principal | APIs nativas de Swift y manejo de Colecciones. | Interoperabilidad con frameworks de Objective-C. |
| Fiabilidad | Muy alta. Siempre extrae los caracteres esperados. | Baja. Puede cortar emojis o caracteres compuestos por la mitad. |
Por ejemplo, con la cadena "Gato!🐱!!", un NSRange que intente extraer 3 "caracteres" a partir del índice 4 podría fallar o devolver resultados inesperados, porque el emoji del gato ocupa más de una unidad de código UTF-16. En cambio, un Range<String.Index> de Swift siempre funcionará correctamente. Si necesitas usar un NSRange en Swift, puedes convertirlo a un Range seguro:
let texto = "Gato!🐱!!" let nsRange = NSRange(location: 4, length: 2) // Ejemplo de NSRange recibido de una API if let rangoSwift = Range(nsRange, in: texto) { let subcadena = texto[rangoSwift] // Contiene "🐱!" de forma segura }Preguntas Frecuentes (FAQ)
P: ¿Entonces, nunca debo guardar un `Substring`?
R: No es que "nunca", pero debes ser consciente de sus implicaciones. Guárdalo solo para operaciones breves y de alcance limitado. Para almacenamiento a largo plazo, como en propiedades de una clase, elementos de un array o para pasarlo a otro sistema, siempre conviértelo a `String` con `String(miSubstring)`. Esto rompe la referencia al string original y optimiza el uso de la memoria.
P: ¿Qué pasó con `substring(with:)`?
R: Fue deprecado en Swift 5. La forma moderna y recomendada es usar el subíndice con un rango, como `miString[miRango]`. Este cambio hace que el manejo de `String` sea más consistente con otras colecciones en Swift, como `Array` o `Dictionary`, promoviendo una sintaxis más unificada y legible.
P: ¿Por qué mi código `miString[2]` da un error?
R: Porque Swift no permite indexar `String` con enteros (`Int`) debido a la complejidad y el tamaño variable de los caracteres Unicode. Esta prohibición te protege de errores sutiles y problemas de rendimiento. Debes usar el tipo `String.Index` y los métodos del `String` para calcular las posiciones correctas y garantizar la precisión.
P: ¿`String` es una Colección en Swift?
R: ¡Sí! A partir de Swift 4, `String` conforma de nuevo al protocolo `Collection`. Esto es una gran ventaja, ya que significa que puedes iterar sobre sus caracteres con un bucle `for-in`, y usar métodos de alto orden como `map`, `filter`, `reduce`, y muchos más, exactamente como lo harías con un `Array`.
Conclusión
La distinción entre String y Substring en Swift es una decisión de diseño inteligente y orientada al rendimiento. Al utilizar Substring como una vista temporal y liviana, Swift evita costosas copias de memoria durante la manipulación de cadenas. Como desarrolladores, nuestra responsabilidad es entender este mecanismo: usar Substring para operaciones intermedias y convertirlo a String para el almacenamiento final. Comprender esto, junto con el uso correcto de String.Index, no solo hará que tu código sea más seguro y correcto, sino también significativamente más eficiente.
Si quieres conocer otros artículos parecidos a Swift: El Secreto de String y Substring puedes visitar la categoría Juegos.
